aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore121
-rw-r--r--COPYING361
-rw-r--r--Documentation/.gitignore6
-rw-r--r--Documentation/Makefile101
-rw-r--r--Documentation/SubmittingPatches298
-rw-r--r--Documentation/asciidoc.conf26
-rwxr-xr-xDocumentation/build-docdep.perl50
-rw-r--r--Documentation/cvs-migration.txt247
-rw-r--r--Documentation/diff-format.txt148
-rw-r--r--Documentation/diff-options.txt62
-rw-r--r--Documentation/diffcore.txt275
-rw-r--r--Documentation/fetch-options.txt19
-rw-r--r--Documentation/git-add.txt75
-rw-r--r--Documentation/git-am.txt96
-rw-r--r--Documentation/git-apply.txt104
-rw-r--r--Documentation/git-applymbox.txt92
-rw-r--r--Documentation/git-applypatch.txt50
-rw-r--r--Documentation/git-archimport.txt85
-rw-r--r--Documentation/git-bisect.txt100
-rw-r--r--Documentation/git-branch.txt46
-rw-r--r--Documentation/git-cat-file.txt60
-rw-r--r--Documentation/git-check-ref-format.txt50
-rw-r--r--Documentation/git-checkout-index.txt99
-rw-r--r--Documentation/git-checkout.txt79
-rw-r--r--Documentation/git-cherry-pick.txt56
-rw-r--r--Documentation/git-cherry.txt42
-rw-r--r--Documentation/git-clone-pack.txt60
-rw-r--r--Documentation/git-clone.txt89
-rw-r--r--Documentation/git-commit-tree.txt99
-rw-r--r--Documentation/git-commit.txt71
-rw-r--r--Documentation/git-convert-objects.txt29
-rw-r--r--Documentation/git-count-objects.txt28
-rw-r--r--Documentation/git-cvsexportcommit.txt56
-rw-r--r--Documentation/git-cvsimport.txt112
-rw-r--r--Documentation/git-daemon.txt71
-rw-r--r--Documentation/git-diff-files.txt43
-rw-r--r--Documentation/git-diff-index.txt133
-rw-r--r--Documentation/git-diff-stages.txt40
-rw-r--r--Documentation/git-diff-tree.txt141
-rw-r--r--Documentation/git-diff.txt52
-rw-r--r--Documentation/git-fetch-pack.txt68
-rw-r--r--Documentation/git-fetch.txt48
-rw-r--r--Documentation/git-fmt-merge-msg.txt39
-rw-r--r--Documentation/git-format-patch.txt93
-rw-r--r--Documentation/git-fsck-objects.txt144
-rw-r--r--Documentation/git-get-tar-commit-id.txt37
-rw-r--r--Documentation/git-grep.txt46
-rw-r--r--Documentation/git-hash-object.txt43
-rw-r--r--Documentation/git-http-fetch.txt41
-rw-r--r--Documentation/git-http-push.txt89
-rw-r--r--Documentation/git-index-pack.txt44
-rw-r--r--Documentation/git-init-db.txt40
-rw-r--r--Documentation/git-local-fetch.txt43
-rw-r--r--Documentation/git-log.txt62
-rw-r--r--Documentation/git-lost-found.txt78
-rw-r--r--Documentation/git-ls-files.txt213
-rw-r--r--Documentation/git-ls-remote.txt63
-rw-r--r--Documentation/git-ls-tree.txt58
-rw-r--r--Documentation/git-mailinfo.txt65
-rw-r--r--Documentation/git-mailsplit.txt45
-rw-r--r--Documentation/git-merge-base.txt33
-rw-r--r--Documentation/git-merge-index.txt88
-rw-r--r--Documentation/git-merge-one-file.txt30
-rw-r--r--Documentation/git-merge.txt56
-rw-r--r--Documentation/git-mktag.txt47
-rw-r--r--Documentation/git-mv.txt51
-rw-r--r--Documentation/git-name-rev.txt66
-rw-r--r--Documentation/git-octopus.txt38
-rw-r--r--Documentation/git-pack-objects.txt89
-rw-r--r--Documentation/git-pack-redundant.txt58
-rw-r--r--Documentation/git-parse-remote.txt48
-rw-r--r--Documentation/git-patch-id.txt43
-rw-r--r--Documentation/git-peek-remote.txt52
-rw-r--r--Documentation/git-prune-packed.txt48
-rw-r--r--Documentation/git-prune.txt42
-rw-r--r--Documentation/git-pull.txt126
-rw-r--r--Documentation/git-push.txt45
-rw-r--r--Documentation/git-read-tree.txt281
-rw-r--r--Documentation/git-rebase.txt35
-rw-r--r--Documentation/git-receive-pack.txt95
-rw-r--r--Documentation/git-relink.txt37
-rw-r--r--Documentation/git-repack.txt59
-rw-r--r--Documentation/git-request-pull.txt40
-rw-r--r--Documentation/git-reset.txt45
-rw-r--r--Documentation/git-resolve.txt36
-rw-r--r--Documentation/git-rev-list.txt150
-rw-r--r--Documentation/git-rev-parse.txt174
-rw-r--r--Documentation/git-revert.txt48
-rw-r--r--Documentation/git-send-email.txt80
-rw-r--r--Documentation/git-send-pack.txt110
-rw-r--r--Documentation/git-sh-setup.txt35
-rw-r--r--Documentation/git-shell.txt35
-rw-r--r--Documentation/git-shortlog.txt30
-rw-r--r--Documentation/git-show-branch.txt113
-rw-r--r--Documentation/git-show-index.txt35
-rw-r--r--Documentation/git-ssh-fetch.txt51
-rw-r--r--Documentation/git-ssh-upload.txt47
-rw-r--r--Documentation/git-status.txt45
-rw-r--r--Documentation/git-stripspace.txt33
-rw-r--r--Documentation/git-svnimport.txt137
-rw-r--r--Documentation/git-symbolic-ref.txt52
-rw-r--r--Documentation/git-tag.txt47
-rw-r--r--Documentation/git-tar-tree.txt38
-rw-r--r--Documentation/git-unpack-file.txt36
-rw-r--r--Documentation/git-unpack-objects.txt42
-rw-r--r--Documentation/git-update-index.txt166
-rw-r--r--Documentation/git-update-ref.txt58
-rw-r--r--Documentation/git-update-server-info.txt58
-rw-r--r--Documentation/git-upload-pack.txt39
-rw-r--r--Documentation/git-var.txt61
-rw-r--r--Documentation/git-verify-pack.txt52
-rw-r--r--Documentation/git-verify-tag.txt32
-rw-r--r--Documentation/git-whatchanged.txt81
-rw-r--r--Documentation/git-write-tree.txt42
-rw-r--r--Documentation/git.txt570
-rw-r--r--Documentation/gitk.txt51
-rw-r--r--Documentation/glossary.txt242
-rw-r--r--Documentation/hooks.txt127
-rwxr-xr-xDocumentation/howto-index.sh56
-rw-r--r--Documentation/howto/isolate-bugs-with-bisect.txt65
-rw-r--r--Documentation/howto/make-dist.txt52
-rw-r--r--Documentation/howto/rebase-and-edit.txt81
-rw-r--r--Documentation/howto/rebase-from-internal-branch.txt168
-rw-r--r--Documentation/howto/rebuild-from-update-hook.txt83
-rw-r--r--Documentation/howto/revert-branch-rebase.txt200
-rw-r--r--Documentation/howto/using-topic-branches.txt288
-rwxr-xr-xDocumentation/install-webdoc.sh25
-rw-r--r--Documentation/merge-options.txt16
-rw-r--r--Documentation/merge-strategies.txt35
-rw-r--r--Documentation/pack-protocol.txt38
-rw-r--r--Documentation/pull-fetch-param.txt124
-rw-r--r--Documentation/repository-layout.txt128
-rw-r--r--Documentation/sort_glossary.pl70
-rw-r--r--Documentation/technical/trivial-merge.txt121
-rw-r--r--Documentation/tutorial.txt1734
-rw-r--r--INSTALL89
-rw-r--r--Makefile482
-rw-r--r--README586
-rw-r--r--apply.c1858
-rw-r--r--arm/sha1.c82
-rw-r--r--arm/sha1.h18
-rw-r--r--arm/sha1_arm.S184
-rw-r--r--blob.c52
-rw-r--r--blob.h18
-rw-r--r--cache.h421
-rw-r--r--cat-file.c56
-rw-r--r--check-ref-format.c17
-rw-r--r--checkout-index.c169
-rw-r--r--clone-pack.c305
-rwxr-xr-xcmd-rename.sh62
-rw-r--r--commit-tree.c129
-rw-r--r--commit.c637
-rw-r--r--commit.h75
-rw-r--r--compat/mmap.c50
-rw-r--r--compat/strcasestr.c23
-rw-r--r--compat/subprocess.py1149
-rw-r--r--config.c254
-rw-r--r--connect.c534
-rw-r--r--convert-objects.c325
-rw-r--r--copy.c40
-rw-r--r--count-delta.c79
-rw-r--r--count-delta.h10
-rw-r--r--csum-file.c148
-rw-r--r--csum-file.h19
-rw-r--r--ctype.c23
-rw-r--r--daemon.c640
-rw-r--r--date.c646
-rw-r--r--debian/.gitignore6
-rw-r--r--debian/changelog111
-rw-r--r--debian/compat1
-rw-r--r--debian/control63
-rw-r--r--debian/copyright24
-rw-r--r--debian/docs1
-rw-r--r--debian/git-arch.files2
-rw-r--r--debian/git-core.doc-base13
-rw-r--r--debian/git-core.files1
-rw-r--r--debian/git-cvs.files2
-rw-r--r--debian/git-doc.files4
-rw-r--r--debian/git-email.files2
-rw-r--r--debian/git-svn.files2
-rw-r--r--debian/git-tk.files2
-rwxr-xr-xdebian/rules109
-rw-r--r--delta.h34
-rw-r--r--diff-delta.c334
-rw-r--r--diff-files.c132
-rw-r--r--diff-index.c247
-rw-r--r--diff-stages.c98
-rw-r--r--diff-tree.c270
-rw-r--r--diff.c1342
-rw-r--r--diff.h139
-rw-r--r--diffcore-break.c283
-rw-r--r--diffcore-order.c122
-rw-r--r--diffcore-pathspec.c67
-rw-r--r--diffcore-pickaxe.c104
-rw-r--r--diffcore-rename.c460
-rw-r--r--diffcore.h104
-rw-r--r--entry.c156
-rw-r--r--environment.c76
-rw-r--r--epoch.c639
-rw-r--r--epoch.h21
-rw-r--r--fetch-pack.c476
-rw-r--r--fetch.c238
-rw-r--r--fetch.h51
-rw-r--r--fsck-objects.c550
-rw-r--r--get-tar-commit-id.c30
-rwxr-xr-xgit-add.sh43
-rwxr-xr-xgit-am.sh407
-rwxr-xr-xgit-applymbox.sh118
-rwxr-xr-xgit-applypatch.sh197
-rwxr-xr-xgit-archimport.perl852
-rwxr-xr-xgit-bisect.sh223
-rwxr-xr-xgit-branch.sh117
-rwxr-xr-xgit-checkout.sh139
-rwxr-xr-xgit-cherry.sh91
-rwxr-xr-xgit-clone.sh224
-rwxr-xr-xgit-commit.sh244
-rwxr-xr-xgit-count-objects.sh13
-rwxr-xr-xgit-cvsexportcommit.perl225
-rwxr-xr-xgit-cvsimport.perl842
-rwxr-xr-xgit-diff.sh47
-rwxr-xr-xgit-fetch.sh343
-rwxr-xr-xgit-fmt-merge-msg.perl95
-rwxr-xr-xgit-format-patch.sh285
-rwxr-xr-xgit-grep.sh47
-rwxr-xr-xgit-log.sh15
-rwxr-xr-xgit-lost-found.sh23
-rwxr-xr-xgit-ls-remote.sh109
-rwxr-xr-xgit-merge-octopus.sh101
-rwxr-xr-xgit-merge-one-file.sh108
-rwxr-xr-xgit-merge-ours.sh14
-rwxr-xr-xgit-merge-recursive.py879
-rwxr-xr-xgit-merge-resolve.sh54
-rwxr-xr-xgit-merge-stupid.sh80
-rwxr-xr-xgit-merge.sh290
-rwxr-xr-xgit-mv.perl197
-rwxr-xr-xgit-octopus.sh90
-rwxr-xr-xgit-parse-remote.sh189
-rwxr-xr-xgit-prune.sh40
-rwxr-xr-xgit-pull.sh108
-rwxr-xr-xgit-push.sh65
-rwxr-xr-xgit-rebase.sh29
-rwxr-xr-xgit-relink.perl173
-rwxr-xr-xgit-repack.sh88
-rwxr-xr-xgit-request-pull.sh35
-rwxr-xr-xgit-reset.sh105
-rwxr-xr-xgit-resolve.sh104
-rwxr-xr-xgit-revert.sh169
-rwxr-xr-xgit-send-email.perl368
-rwxr-xr-xgit-sh-setup.sh25
-rwxr-xr-xgit-shortlog.perl200
-rwxr-xr-xgit-status.sh106
-rwxr-xr-xgit-svnimport.perl780
-rwxr-xr-xgit-tag.sh104
-rwxr-xr-xgit-verify-tag.sh12
-rwxr-xr-xgit-whatchanged.sh7
-rw-r--r--git.c298
-rw-r--r--git.spec.in188
-rw-r--r--gitMergeCommon.py272
-rw-r--r--hash-object.c45
-rw-r--r--http-fetch.c1426
-rw-r--r--http-push.c1829
-rw-r--r--ident.c192
-rw-r--r--index-pack.c462
-rw-r--r--index.c55
-rw-r--r--init-db.c268
-rw-r--r--local-fetch.c245
-rw-r--r--ls-files.c687
-rw-r--r--ls-tree.c252
-rw-r--r--mailinfo.c753
-rw-r--r--mailsplit.c157
-rw-r--r--merge-base.c256
-rw-r--r--merge-index.c135
-rw-r--r--mktag.c136
-rw-r--r--mozilla-sha1/sha1.c152
-rw-r--r--mozilla-sha1/sha1.h45
-rw-r--r--name-rev.c246
-rw-r--r--object.c249
-rw-r--r--object.h58
-rw-r--r--pack-check.c143
-rw-r--r--pack-objects.c560
-rw-r--r--pack-redundant.c652
-rw-r--r--pack.h32
-rw-r--r--patch-delta.c73
-rw-r--r--patch-id.c82
-rw-r--r--path.c91
-rw-r--r--peek-remote.c55
-rw-r--r--pkt-line.c117
-rw-r--r--pkt-line.h12
-rw-r--r--ppc/sha1.c72
-rw-r--r--ppc/sha1.h20
-rw-r--r--ppc/sha1ppc.S185
-rw-r--r--prune-packed.c77
-rw-r--r--quote.c257
-rw-r--r--quote.h47
-rw-r--r--read-cache.c591
-rw-r--r--read-tree.c728
-rw-r--r--receive-pack.c291
-rw-r--r--refs.c410
-rw-r--r--refs.h28
-rw-r--r--rev-list.c881
-rw-r--r--rev-parse.c291
-rw-r--r--rsh.c112
-rw-r--r--rsh.h7
-rw-r--r--run-command.c58
-rw-r--r--run-command.h17
-rw-r--r--send-pack.c315
-rw-r--r--server-info.c534
-rw-r--r--setup.c129
-rw-r--r--sha1_file.c1579
-rw-r--r--sha1_name.c444
-rw-r--r--shell.c59
-rw-r--r--show-branch.c571
-rw-r--r--show-index.c28
-rw-r--r--ssh-fetch.c170
-rw-r--r--ssh-pull.c4
-rw-r--r--ssh-push.c4
-rw-r--r--ssh-upload.c143
-rw-r--r--strbuf.c44
-rw-r--r--strbuf.h13
-rw-r--r--stripspace.c48
-rw-r--r--symbolic-ref.c35
-rw-r--r--t/Makefile28
-rw-r--r--t/README208
-rwxr-xr-xt/diff-lib.sh41
-rwxr-xr-xt/lib-read-tree-m-3way.sh158
-rwxr-xr-xt/t0000-basic.sh180
-rwxr-xr-xt/t1000-read-tree-m-3way.sh514
-rwxr-xr-xt/t1001-read-tree-m-2way.sh344
-rwxr-xr-xt/t1002-read-tree-m-u-2way.sh324
-rwxr-xr-xt/t1100-commit-tree-options.sh45
-rw-r--r--t/t1200-tutorial.sh160
-rwxr-xr-xt/t2000-checkout-cache-clash.sh53
-rwxr-xr-xt/t2001-checkout-cache-clash.sh87
-rwxr-xr-xt/t2002-checkout-cache-u.sh33
-rwxr-xr-xt/t2003-checkout-cache-mkdir.sh96
-rwxr-xr-xt/t2100-update-cache-badpath.sh51
-rwxr-xr-xt/t3000-ls-files-others.sh34
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh82
-rwxr-xr-xt/t3002-ls-files-dashpath.sh69
-rwxr-xr-xt/t3010-ls-files-killed-modified.sh96
-rwxr-xr-xt/t3100-ls-tree-restrict.sh131
-rw-r--r--t/t3101-ls-tree-dirname.sh160
-rwxr-xr-xt/t3200-branch.sh35
-rwxr-xr-xt/t3300-funny-names.sh142
-rwxr-xr-xt/t4000-diff-format.sh54
-rwxr-xr-xt/t4001-diff-rename.sh67
-rwxr-xr-xt/t4002-diff-basic.sh247
-rwxr-xr-xt/t4003-diff-rename-1.sh128
-rwxr-xr-xt/t4004-diff-rename-symlink.sh67
-rwxr-xr-xt/t4005-diff-rename-2.sh86
-rwxr-xr-xt/t4006-diff-mode.sh34
-rwxr-xr-xt/t4007-rename-3.sh90
-rwxr-xr-xt/t4008-diff-break-rewrite.sh188
-rwxr-xr-xt/t4009-diff-rename-4.sh95
-rwxr-xr-xt/t4010-diff-pathspec.sh65
-rwxr-xr-xt/t4100-apply-stat.sh47
-rw-r--r--t/t4100/t-apply-1.expect11
-rw-r--r--t/t4100/t-apply-1.patch194
-rw-r--r--t/t4100/t-apply-2.expect5
-rw-r--r--t/t4100/t-apply-2.patch72
-rw-r--r--t/t4100/t-apply-3.expect7
-rw-r--r--t/t4100/t-apply-3.patch567
-rw-r--r--t/t4100/t-apply-4.expect5
-rw-r--r--t/t4100/t-apply-4.patch7
-rw-r--r--t/t4100/t-apply-5.expect19
-rw-r--r--t/t4100/t-apply-5.patch612
-rw-r--r--t/t4100/t-apply-6.expect5
-rw-r--r--t/t4100/t-apply-6.patch101
-rw-r--r--t/t4100/t-apply-7.expect6
-rw-r--r--t/t4100/t-apply-7.patch494
-rwxr-xr-xt/t4101-apply-nonl.sh34
-rwxr-xr-xt/t4102-apply-rename.sh37
-rw-r--r--t/t4103-apply-binary.sh115
-rw-r--r--t/t4109-apply-multifrag.sh176
-rw-r--r--t/t4110-apply-scan.sh101
-rwxr-xr-xt/t4112-apply-renames.sh148
-rwxr-xr-xt/t5000-tar-tree.sh95
-rwxr-xr-xt/t5300-pack-object.sh186
-rwxr-xr-xt/t5400-send-pack.sh59
-rw-r--r--t/t5500-fetch-pack.sh136
-rwxr-xr-xt/t5501-old-fetch-and-upload.sh43
-rwxr-xr-xt/t6000lib.sh109
-rwxr-xr-xt/t6001-rev-list-merge-order.sh462
-rwxr-xr-xt/t6002-rev-list-bisect.sh238
-rwxr-xr-xt/t6003-rev-list-topo-order.sh408
-rwxr-xr-xt/t6010-merge-base.sh59
-rw-r--r--t/t6101-rev-parse-parents.sh33
-rwxr-xr-xt/test-lib.sh173
-rw-r--r--tag.c108
-rw-r--r--tag.h20
-rw-r--r--tar-tree.c441
-rw-r--r--templates/.gitignore2
-rw-r--r--templates/Makefile48
-rw-r--r--templates/branches--1
-rw-r--r--templates/hooks--applypatch-msg14
-rw-r--r--templates/hooks--commit-msg14
-rw-r--r--templates/hooks--post-commit8
-rw-r--r--templates/hooks--post-update8
-rw-r--r--templates/hooks--pre-applypatch14
-rw-r--r--templates/hooks--pre-commit61
-rw-r--r--templates/hooks--update31
-rw-r--r--templates/info--exclude6
-rw-r--r--templates/remotes--1
-rw-r--r--templates/this--description1
-rw-r--r--test-date.c23
-rw-r--r--test-delta.c83
-rw-r--r--tree-diff.c270
-rw-r--r--tree.c246
-rw-r--r--tree.h38
-rw-r--r--unpack-file.c34
-rw-r--r--unpack-objects.c316
-rw-r--r--update-index.c518
-rw-r--r--update-ref.c84
-rw-r--r--update-server-info.c23
-rw-r--r--upload-pack.c292
-rw-r--r--usage.c39
-rw-r--r--var.c76
-rw-r--r--verify-pack.c57
-rw-r--r--write-tree.c152
419 files changed, 68035 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..0dd7b9c7b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,121 @@
+git
+git-add
+git-am
+git-apply
+git-applymbox
+git-applypatch
+git-archimport
+git-bisect
+git-branch
+git-cat-file
+git-check-ref-format
+git-checkout
+git-checkout-index
+git-cherry
+git-cherry-pick
+git-clone
+git-clone-pack
+git-commit
+git-commit-tree
+git-convert-objects
+git-count-objects
+git-cvsexportcommit
+git-cvsimport
+git-daemon
+git-diff
+git-diff-files
+git-diff-index
+git-diff-stages
+git-diff-tree
+git-fetch
+git-fetch-pack
+git-findtags
+git-fmt-merge-msg
+git-format-patch
+git-fsck-objects
+git-get-tar-commit-id
+git-grep
+git-hash-object
+git-http-fetch
+git-http-push
+git-index-pack
+git-init-db
+git-local-fetch
+git-log
+git-lost-found
+git-ls-files
+git-ls-remote
+git-ls-tree
+git-mailinfo
+git-mailsplit
+git-merge
+git-merge-base
+git-merge-index
+git-merge-octopus
+git-merge-one-file
+git-merge-ours
+git-merge-recursive
+git-merge-resolve
+git-merge-stupid
+git-mktag
+git-name-rev
+git-mv
+git-octopus
+git-pack-redundant
+git-pack-objects
+git-parse-remote
+git-patch-id
+git-peek-remote
+git-prune
+git-prune-packed
+git-pull
+git-push
+git-read-tree
+git-rebase
+git-receive-pack
+git-relink
+git-repack
+git-request-pull
+git-reset
+git-resolve
+git-rev-list
+git-rev-parse
+git-revert
+git-send-email
+git-send-pack
+git-sh-setup
+git-shell
+git-shortlog
+git-show-branch
+git-show-index
+git-ssh-fetch
+git-ssh-pull
+git-ssh-push
+git-ssh-upload
+git-status
+git-stripspace
+git-svnimport
+git-symbolic-ref
+git-tag
+git-tar-tree
+git-unpack-file
+git-unpack-objects
+git-update-index
+git-update-ref
+git-update-server-info
+git-upload-pack
+git-var
+git-verify-pack
+git-verify-tag
+git-whatchanged
+git-write-tree
+git-core-*/?*
+test-date
+test-delta
+*.tar.gz
+*.dsc
+*.deb
+git-core.spec
+*.exe
+libgit.a
+*.o
diff --git a/COPYING b/COPYING
new file mode 100644
index 000000000..6ff87c466
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,361 @@
+
+ Note that the only valid version of the GPL as far as this project
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+
+ HOWEVER, in order to allow a migration to GPLv3 if that seems like
+ a good idea, I also ask that people involved with the project make
+ their preferences known. In particular, if you trust me to make that
+ decision, you might note so in your copyright message, ie something
+ like
+
+ This file is licensed under the GPL v2, or a later version
+ at the discretion of Linus.
+
+ might avoid issues. But we can also just decide to synchronize and
+ contact all copyright holders on record if/when the occasion arises.
+
+ Linus Torvalds
+
+----------------------------------------
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/Documentation/.gitignore b/Documentation/.gitignore
new file mode 100644
index 000000000..9fef49087
--- /dev/null
+++ b/Documentation/.gitignore
@@ -0,0 +1,6 @@
+*.xml
+*.html
+*.1
+*.7
+howto-index.txt
+doc.dep
diff --git a/Documentation/Makefile b/Documentation/Makefile
new file mode 100644
index 000000000..be4f3e13c
--- /dev/null
+++ b/Documentation/Makefile
@@ -0,0 +1,101 @@
+MAN1_TXT=$(wildcard git-*.txt) gitk.txt
+MAN7_TXT=git.txt
+
+DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN7_TXT))
+
+ARTICLES = tutorial
+ARTICLES += cvs-migration
+ARTICLES += diffcore
+ARTICLES += howto-index
+ARTICLES += repository-layout
+ARTICLES += hooks
+# with their own formatting rules.
+SP_ARTICLES = glossary howto/revert-branch-rebase
+
+DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
+
+DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
+DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
+
+prefix?=$(HOME)
+bin=$(prefix)/bin
+mandir=$(prefix)/man
+man1=$(mandir)/man1
+man7=$(mandir)/man7
+# DESTDIR=
+
+INSTALL?=install
+
+#
+# Please note that there is a minor bug in asciidoc.
+# The version after 6.0.3 _will_ include the patch found here:
+# http://marc.theaimsgroup.com/?l=git&m=111558757202243&w=2
+#
+# Until that version is released you may have to apply the patch
+# yourself - yes, all 6 characters of it!
+#
+
+all: html man
+
+html: $(DOC_HTML)
+
+
+man: man1 man7
+man1: $(DOC_MAN1)
+man7: $(DOC_MAN7)
+
+install: man
+ $(INSTALL) -d -m755 $(DESTDIR)/$(man1) $(DESTDIR)/$(man7)
+ $(INSTALL) $(DOC_MAN1) $(DESTDIR)/$(man1)
+ $(INSTALL) $(DOC_MAN7) $(DESTDIR)/$(man7)
+
+
+#
+# Determine "include::" file references in asciidoc files.
+#
+doc.dep : $(wildcard *.txt) build-docdep.perl
+ rm -f $@+ $@
+ perl ./build-docdep.perl >$@+
+ mv $@+ $@
+
+-include doc.dep
+
+git.7: ../README
+
+
+clean:
+ rm -f *.xml *.html *.1 *.7 howto-index.txt howto/*.html doc.dep
+
+%.html : %.txt
+ asciidoc -b xhtml11 -d manpage -f asciidoc.conf $<
+
+%.1 %.7 : %.xml
+ xmlto man $<
+
+%.xml : %.txt
+ asciidoc -b docbook -d manpage -f asciidoc.conf $<
+
+git.html: git.txt ../README
+
+glossary.html : glossary.txt sort_glossary.pl
+ cat $< | \
+ perl sort_glossary.pl | \
+ asciidoc -b xhtml11 - > glossary.html
+
+howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
+ rm -f $@+ $@
+ sh ./howto-index.sh $(wildcard howto/*.txt) >$@+
+ mv $@+ $@
+
+$(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
+ asciidoc -b xhtml11 $*.txt
+
+WEBDOC_DEST = /pub/software/scm/git/docs
+
+$(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
+ rm -f $@+ $@
+ sed -e '1,/^$$/d' $? | asciidoc -b xhtml11 - >$@+
+ mv $@+ $@
+
+install-webdoc : html
+ sh ./install-webdoc.sh $(WEBDOC_DEST)
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
new file mode 100644
index 000000000..9ccb8f72e
--- /dev/null
+++ b/Documentation/SubmittingPatches
@@ -0,0 +1,298 @@
+I started reading over the SubmittingPatches document for Linux
+kernel, primarily because I wanted to have a document similar to
+it for the core GIT to make sure people understand what they are
+doing when they write "Signed-off-by" line.
+
+But the patch submission requirements are a lot more relaxed
+here, because the core GIT is thousand times smaller ;-). So
+here is only the relevant bits.
+
+
+(1) Make separate commits for logically separate changes.
+
+Unless your patch is really trivial, you should not be sending
+out a patch that was generated between your working tree and
+your commit head. Instead, always make a commit with complete
+commit message and generate a series of patches from your
+repository. It is a good discipline.
+
+Describe the technical detail of the change(s).
+
+If your description starts to get long, that's a sign that you
+probably need to split up your commit to finer grained pieces.
+
+
+(2) Generate your patch using git/cogito out of your commits.
+
+git diff tools generate unidiff which is the preferred format.
+You do not have to be afraid to use -M option to "git diff" or
+"git format-patch", if your patch involves file renames. The
+receiving end can handle them just fine.
+
+Please make sure your patch does not include any extra files
+which do not belong in a patch submission. Make sure to review
+your patch after generating it, to ensure accuracy. Before
+sending out, please make sure it cleanly applies to the "master"
+branch head.
+
+
+(3) Sending your patches.
+
+People on the git mailing list needs to be able to read and
+comment on the changes you are submitting. It is important for
+a developer to be able to "quote" your changes, using standard
+e-mail tools, so that they may comment on specific portions of
+your code. For this reason, all patches should be submitting
+e-mail "inline". WARNING: Be wary of your MUAs word-wrap
+corrupting your patch. Do not cut-n-paste your patch.
+
+It is common convention to prefix your subject line with
+[PATCH]. This lets people easily distinguish patches from other
+e-mail discussions.
+
+"git format-patch" command follows the best current practice to
+format the body of an e-mail message. At the beginning of the
+patch should come your commit message, ending with the
+Signed-off-by: lines, and a line that consists of three dashes,
+followed by the diffstat information and the patch itself. If
+you are forwarding a patch from somebody else, optionally, at
+the beginning of the e-mail message just before the commit
+message starts, you can put a "From: " line to name that person.
+
+You often want to add additional explanation about the patch,
+other than the commit message itself. Place such "cover letter"
+material between the three dash lines and the diffstat.
+
+Do not attach the patch as a MIME attachment, compressed or not.
+Do not let your e-mail client send quoted-printable. Many
+popular e-mail applications will not always transmit a MIME
+attachment as plain text, making it impossible to comment on
+your code. A MIME attachment also takes a bit more time to
+process. This does not decrease the likelihood of your
+MIME-attached change being accepted, but it makes it more likely
+that it will be postponed.
+
+Exception: If your mailer is mangling patches then someone may ask
+you to re-send them using MIME, that is OK.
+
+Do not PGP sign your patch, at least for now. Most likely, your
+maintainer or other people on the list would not have your PGP
+key and would not bother obtaining it anyway. Your patch is not
+judged by who you are; a good patch from an unknown origin has a
+far better chance of being accepted than a patch from a known,
+respected origin that is done poorly or does incorrect things.
+
+If you really really really really want to do a PGP signed
+patch, format it as "multipart/signed", not a text/plain message
+that starts with '-----BEGIN PGP SIGNED MESSAGE-----'. That is
+not a text/plain, it's something else.
+
+Note that your maintainer does not necessarily read everything
+on the git mailing list. If your patch is for discussion first,
+send it "To:" the mailing list, and optionally "cc:" him. If it
+is trivially correct or after the list reached a consensus, send
+it "To:" the maintainer and optionally "cc:" the list.
+
+
+(6) Sign your work
+
+To improve tracking of who did what, we've borrowed the
+"sign-off" procedure from the Linux kernel project on patches
+that are being emailed around. Although core GIT is a lot
+smaller project it is a good discipline to follow it.
+
+The sign-off is a simple line at the end of the explanation for
+the patch, which certifies that you wrote it or otherwise have
+the right to pass it on as a open-source patch. The rules are
+pretty simple: if you can certify the below:
+
+ Developer's Certificate of Origin 1.1
+
+ By making a contribution to this project, I certify that:
+
+ (a) The contribution was created in whole or in part by me and I
+ have the right to submit it under the open source license
+ indicated in the file; or
+
+ (b) The contribution is based upon previous work that, to the best
+ of my knowledge, is covered under an appropriate open source
+ license and I have the right under that license to submit that
+ work with modifications, whether created in whole or in part
+ by me, under the same open source license (unless I am
+ permitted to submit under a different license), as indicated
+ in the file; or
+
+ (c) The contribution was provided directly to me by some other
+ person who certified (a), (b) or (c) and I have not modified
+ it.
+
+ (d) I understand and agree that this project and the contribution
+ are public and that a record of the contribution (including all
+ personal information I submit with it, including my sign-off) is
+ maintained indefinitely and may be redistributed consistent with
+ this project or the open source license(s) involved.
+
+then you just add a line saying
+
+ Signed-off-by: Random J Developer <random@developer.example.org>
+
+Some people also put extra tags at the end. They'll just be ignored for
+now, but you can do this to mark internal company procedures or just
+point out some special detail about the sign-off.
+
+
+------------------------------------------------
+MUA specific hints
+
+Some of patches I receive or pick up from the list share common
+patterns of breakage. Please make sure your MUA is set up
+properly not to corrupt whitespaces. Here are two common ones
+I have seen:
+
+* Empty context lines that do not have _any_ whitespace.
+
+* Non empty context lines that have one extra whitespace at the
+ beginning.
+
+One test you could do yourself if your MUA is set up correctly is:
+
+* Send the patch to yourself, exactly the way you would, except
+ To: and Cc: lines, which would not contain the list and
+ maintainer address.
+
+* Save that patch to a file in UNIX mailbox format. Call it say
+ a.patch.
+
+* Try to apply to the tip of the "master" branch from the
+ git.git public repository:
+
+ $ git fetch http://kernel.org/pub/scm/git/git.git master:test-apply
+ $ git checkout test-apply
+ $ git reset --hard
+ $ git applymbox a.patch
+
+If it does not apply correctly, there can be various reasons.
+
+* Your patch itself does not apply cleanly. That is _bad_ but
+ does not have much to do with your MUA. Please rebase the
+ patch appropriately.
+
+* Your MUA corrupted your patch; applymbox would complain that
+ the patch does not apply. Look at .dotest/ subdirectory and
+ see what 'patch' file contains and check for the common
+ corruption patterns mentioned above.
+
+* While you are at it, check what are in 'info' and
+ 'final-commit' files as well. If what is in 'final-commit' is
+ not exactly what you would want to see in the commit log
+ message, it is very likely that your maintainer would end up
+ hand editing the log message when he applies your patch.
+ Things like "Hi, this is my first patch.\n", if you really
+ want to put in the patch e-mail, should come after the
+ three-dash line that signals the end of the commit message.
+
+
+Pine
+----
+
+(Johannes Schindelin)
+
+I don't know how many people still use pine, but for those poor
+souls it may be good to mention that the quell-flowed-text is
+needed for recent versions.
+
+... the "no-strip-whitespace-before-send" option, too. AFAIK it
+was introduced in 4.60.
+
+(Linus Torvalds)
+
+And 4.58 needs at least this.
+
+---
+diff-tree 8326dd8350be64ac7fc805f6563a1d61ad10d32c (from e886a61f76edf5410573e92e38ce22974f9c40f1)
+Author: Linus Torvalds <torvalds@g5.osdl.org>
+Date: Mon Aug 15 17:23:51 2005 -0700
+
+ Fix pine whitespace-corruption bug
+
+ There's no excuse for unconditionally removing whitespace from
+ the pico buffers on close.
+
+diff --git a/pico/pico.c b/pico/pico.c
+--- a/pico/pico.c
++++ b/pico/pico.c
+@@ -219,7 +219,9 @@ PICO *pm;
+ switch(pico_all_done){ /* prepare for/handle final events */
+ case COMP_EXIT : /* already confirmed */
+ packheader();
++#if 0
+ stripwhitespace();
++#endif
+ c |= COMP_EXIT;
+ break;
+
+
+(Daniel Barkalow)
+
+> A patch to SubmittingPatches, MUA specific help section for
+> users of Pine 4.63 would be very much appreciated.
+
+Ah, it looks like a recent version changed the default behavior to do the
+right thing, and inverted the sense of the configuration option. (Either
+that or Gentoo did it.) So you need to set the
+"no-strip-whitespace-before-send" option, unless the option you have is
+"strip-whitespace-before-send", in which case you should avoid checking
+it.
+
+
+Thunderbird
+-----------
+
+(A Large Angry SCM)
+
+Here are some hints on how to successfully submit patches inline using
+Thunderbird.
+
+This recipe appears to work with the current [*1*] Thunderbird from Suse.
+
+The following Thunderbird extensions are needed:
+ AboutConfig 0.5
+ http://aboutconfig.mozdev.org/
+ External Editor 0.5.4
+ http://extensionroom.mozdev.org/more-info/exteditor
+
+1) Prepare the patch as a text file using your method of choice.
+
+2) Before opening a compose window, use Edit->Account Settings to
+uncheck the "Compose messages in HTML format" setting in the
+"Composition & Addressing" panel of the account to be used to send the
+patch. [*2*]
+
+3) In the main Thunderbird window, _before_ you open the compose window
+for the patch, use Tools->about:config to set the following to the
+indicated values:
+ mailnews.send_plaintext_flowed => false
+ mailnews.wraplength => 0
+
+4) Open a compose window and click the external editor icon.
+
+5) In the external editor window, read in the patch file and exit the
+editor normally.
+
+6) Back in the compose window: Add whatever other text you wish to the
+message, complete the addressing and subject fields, and press send.
+
+7) Optionally, undo the about:config/account settings changes made in
+steps 2 & 3.
+
+
+[Footnotes]
+*1* Version 1.0 (20041207) from the MozillaThunderbird-1.0-5 rpm of Suse
+9.3 professional updates.
+
+*2* It may be possible to do this with about:config and the following
+settings but I haven't tried, yet.
+ mail.html_compose => false
+ mail.identity.default.compose_html => false
+ mail.identity.id?.compose_html => false
+
diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf
new file mode 100644
index 000000000..fa0877d48
--- /dev/null
+++ b/Documentation/asciidoc.conf
@@ -0,0 +1,26 @@
+## gitlink: macro
+#
+# Usage: gitlink:command[manpage-section]
+#
+# Note, {0} is the manpage section, while {target} is the command.
+#
+# Show GIT link as: <command>(<section>); if section is defined, else just show
+# the command.
+
+[attributes]
+caret=^
+
+ifdef::backend-docbook[]
+[gitlink-inlinemacro]
+{0%{target}}
+{0#<citerefentry>}
+{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>}
+{0#</citerefentry>}
+endif::backend-docbook[]
+
+ifdef::backend-xhtml11[]
+[gitlink-inlinemacro]
+<a href="{target}.html">{target}{0?({0})}</a>
+endif::backend-xhtml11[]
+
+
diff --git a/Documentation/build-docdep.perl b/Documentation/build-docdep.perl
new file mode 100755
index 000000000..489389c32
--- /dev/null
+++ b/Documentation/build-docdep.perl
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+my %include = ();
+my %included = ();
+
+for my $text (<*.txt>) {
+ open I, '<', $text || die "cannot read: $text";
+ while (<I>) {
+ if (/^include::/) {
+ chomp;
+ s/^include::\s*//;
+ s/\[\]//;
+ $include{$text}{$_} = 1;
+ $included{$_} = 1;
+ }
+ }
+ close I;
+}
+
+# Do we care about chained includes???
+my $changed = 1;
+while ($changed) {
+ $changed = 0;
+ while (my ($text, $included) = each %include) {
+ for my $i (keys %$included) {
+ # $text has include::$i; if $i includes $j
+ # $text indirectly includes $j.
+ if (exists $include{$i}) {
+ for my $j (keys %{$include{$i}}) {
+ if (!exists $include{$text}{$j}) {
+ $include{$text}{$j} = 1;
+ $included{$j} = 1;
+ $changed = 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+while (my ($text, $included) = each %include) {
+ if (! exists $included{$text} &&
+ (my $base = $text) =~ s/\.txt$//) {
+ my ($suffix) = '1';
+ if ($base eq 'git') {
+ $suffix = '7'; # yuck...
+ }
+ print "$base.html $base.$suffix : ", join(" ", keys %$included), "\n";
+ }
+}
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
new file mode 100644
index 000000000..57436f007
--- /dev/null
+++ b/Documentation/cvs-migration.txt
@@ -0,0 +1,247 @@
+git for CVS users
+=================
+
+Ok, so you're a CVS user. That's ok, it's a treatable condition, and the
+first step to recovery is admitting you have a problem. The fact that
+you are reading this file means that you may be well on that path
+already.
+
+The thing about CVS is that it absolutely sucks as a source control
+manager, and you'll thus be happy with almost anything else. git,
+however, may be a bit 'too' different (read: "good") for your taste, and
+does a lot of things differently.
+
+One particular suckage of CVS is very hard to work around: CVS is
+basically a tool for tracking 'file' history, while git is a tool for
+tracking 'project' history. This sometimes causes problems if you are
+used to doing very strange things in CVS, in particular if you're doing
+things like making branches of just a subset of the project. git can't
+track that, since git never tracks things on the level of an individual
+file, only on the whole project level.
+
+The good news is that most people don't do that, and in fact most sane
+people think it's a bug in CVS that makes it tag (and check in changes)
+one file at a time. So most projects you'll ever see will use CVS
+'as if' it was sane. In which case you'll find it very easy indeed to
+move over to git.
+
+First off: this is not a git tutorial. See
+link:tutorial.html[Documentation/tutorial.txt] for how git
+actually works. This is more of a random collection of gotcha's
+and notes on converting from CVS to git.
+
+Second: CVS has the notion of a "repository" as opposed to the thing
+that you're actually working in (your working directory, or your
+"checked out tree"). git does not have that notion at all, and all git
+working directories 'are' the repositories. However, you can easily
+emulate the CVS model by having one special "global repository", which
+people can synchronize with. See details later, but in the meantime
+just keep in mind that with git, every checked out working tree will
+have a full revision control history of its own.
+
+
+Importing a CVS archive
+-----------------------
+
+Ok, you have an old project, and you want to at least give git a chance
+to see how it performs. The first thing you want to do (after you've
+gone through the git tutorial, and generally familiarized yourself with
+how to commit stuff etc in git) is to create a git'ified version of your
+CVS archive.
+
+Happily, that's very easy indeed. git will do it for you, although git
+will need the help of a program called "cvsps":
+
+ http://www.cobite.com/cvsps/
+
+which is not actually related to git at all, but which makes CVS usage
+look almost sane (ie you almost certainly want to have it even if you
+decide to stay with CVS). However, git will want 'at least' version 2.1
+of cvsps (available at the address above), and in fact will currently
+refuse to work with anything else.
+
+Once you've gotten (and installed) cvsps, you may or may not want to get
+any more familiar with it, but make sure it is in your path. After that,
+the magic command line is
+
+ git cvsimport -v -d <cvsroot> -C <destination> <module>
+
+which will do exactly what you'd think it does: it will create a git
+archive of the named CVS module. The new archive will be created in the
+subdirectory named <destination>; it'll be created if it doesn't exist.
+Default is the local directory.
+
+It can take some time to actually do the conversion for a large archive
+since it involves checking out from CVS every revision of every file,
+and the conversion script is reasonably chatty unless you omit the '-v'
+option, but on some not very scientific tests it averaged about twenty
+revisions per second, so a medium-sized project should not take more
+than a couple of minutes. For larger projects or remote repositories,
+the process may take longer.
+
+After the (initial) import is done, the CVS archive's current head
+revision will be checked out -- thus, you can start adding your own
+changes right away.
+
+The import is incremental, i.e. if you call it again next month it'll
+fetch any CVS updates that have been happening in the meantime. The
+cut-off is date-based, so don't change the branches that were imported
+from CVS.
+
+You can merge those updates (or, in fact, a different CVS branch) into
+your main branch:
+
+ git resolve HEAD origin "merge with current CVS HEAD"
+
+The HEAD revision from CVS is named "origin", not "HEAD", because git
+already uses "HEAD". (If you don't like 'origin', use cvsimport's
+'-o' option to change it.)
+
+
+Emulating CVS behaviour
+-----------------------
+
+
+So, by now you are convinced you absolutely want to work with git, but
+at the same time you absolutely have to have a central repository.
+Step back and think again. Okay, you still need a single central
+repository? There are several ways to go about that:
+
+1. Designate a person responsible to pull all branches. Make the
+repository of this person public, and make every team member
+pull regularly from it.
+
+2. Set up a public repository with read/write access for every team
+member. Use "git pull/push" as you used "cvs update/commit". Be
+sure that your repository is up to date before pushing, just
+like you used to do with "cvs commit"; your push will fail if
+what you are pushing is not up to date.
+
+3. Make the repository of every team member public. It is the
+responsibility of each single member to pull from every other
+team member.
+
+
+CVS annotate
+------------
+
+So, something has gone wrong, and you don't know whom to blame, and
+you're an ex-CVS user and used to do "cvs annotate" to see who caused
+the breakage. You're looking for the "git annotate", and it's just
+claiming not to find such a script. You're annoyed.
+
+Yes, that's right. Core git doesn't do "annotate", although it's
+technically possible, and there are at least two specialized scripts out
+there that can be used to get equivalent information (see the git
+mailing list archives for details).
+
+git has a couple of alternatives, though, that you may find sufficient
+or even superior depending on your use. One is called "git-whatchanged"
+(for obvious reasons) and the other one is called "pickaxe" ("a tool for
+the software archeologist").
+
+The "git-whatchanged" script is a truly trivial script that can give you
+a good overview of what has changed in a file or a directory (or an
+arbitrary list of files or directories). The "pickaxe" support is an
+additional layer that can be used to further specify exactly what you're
+looking for, if you already know the specific area that changed.
+
+Let's step back a bit and think about the reason why you would
+want to do "cvs annotate a-file.c" to begin with.
+
+You would use "cvs annotate" on a file when you have trouble
+with a function (or even a single "if" statement in a function)
+that happens to be defined in the file, which does not do what
+you want it to do. And you would want to find out why it was
+written that way, because you are about to modify it to suit
+your needs, and at the same time you do not want to break its
+current callers. For that, you are trying to find out why the
+original author did things that way in the original context.
+
+Many times, it may be enough to see the commit log messages of
+commits that touch the file in question, possibly along with the
+patches themselves, like this:
+
+ $ git-whatchanged -p a-file.c
+
+This will show log messages and patches for each commit that
+touches a-file.
+
+This, however, may not be very useful when this file has many
+modifications that are not related to the piece of code you are
+interested in. You would see many log messages and patches that
+do not have anything to do with the piece of code you are
+interested in. As an example, assuming that you have this piece
+of code that you are interested in in the HEAD version:
+
+ if (frotz) {
+ nitfol();
+ }
+
+you would use git-rev-list and git-diff-tree like this:
+
+ $ git-rev-list HEAD |
+ git-diff-tree --stdin -v -p -S'if (frotz) {
+ nitfol();
+ }'
+
+We have already talked about the "\--stdin" form of git-diff-tree
+command that reads the list of commits and compares each commit
+with its parents. The git-whatchanged command internally runs
+the equivalent of the above command, and can be used like this:
+
+ $ git-whatchanged -p -S'if (frotz) {
+ nitfol();
+ }'
+
+When the -S option is used, git-diff-tree command outputs
+differences between two commits only if one tree has the
+specified string in a file and the corresponding file in the
+other tree does not. The above example looks for a commit that
+has the "if" statement in it in a file, but its parent commit
+does not have it in the same shape in the corresponding file (or
+the other way around, where the parent has it and the commit
+does not), and the differences between them are shown, along
+with the commit message (thanks to the -v flag). It does not
+show anything for commits that do not touch this "if" statement.
+
+Also, in the original context, the same statement might have
+appeared at first in a different file and later the file was
+renamed to "a-file.c". CVS annotate would not help you to go
+back across such a rename, but git would still help you in such
+a situation. For that, you can give the -C flag to
+git-diff-tree, like this:
+
+ $ git-whatchanged -p -C -S'if (frotz) {
+ nitfol();
+ }'
+
+When the -C flag is used, file renames and copies are followed.
+So if the "if" statement in question happens to be in "a-file.c"
+in the current HEAD commit, even if the file was originally
+called "o-file.c" and then renamed in an earlier commit, or if
+the file was created by copying an existing "o-file.c" in an
+earlier commit, you will not lose track. If the "if" statement
+did not change across such a rename or copy, then the commit that
+does rename or copy would not show in the output, and if the
+"if" statement was modified while the file was still called
+"o-file.c", it would find the commit that changed the statement
+when it was in "o-file.c".
+
+NOTE: The current version of "git-diff-tree -C" is not eager
+ enough to find copies, and it will miss the fact that a-file.c
+ was created by copying o-file.c unless o-file.c was somehow
+ changed in the same commit.
+
+You can use the --pickaxe-all flag in addition to the -S flag.
+This causes the differences from all the files contained in
+those two commits, not just the differences between the files
+that contain this changed "if" statement:
+
+ $ git-whatchanged -p -C -S'if (frotz) {
+ nitfol();
+ }' --pickaxe-all
+
+NOTE: This option is called "--pickaxe-all" because -S
+ option is internally called "pickaxe", a tool for software
+ archaeologists.
diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt
new file mode 100644
index 000000000..97756ec03
--- /dev/null
+++ b/Documentation/diff-format.txt
@@ -0,0 +1,148 @@
+The output format from "git-diff-index", "git-diff-tree" and
+"git-diff-files" are very similar.
+
+These commands all compare two sets of things; what is
+compared differs:
+
+git-diff-index <tree-ish>::
+ compares the <tree-ish> and the files on the filesystem.
+
+git-diff-index --cached <tree-ish>::
+ compares the <tree-ish> and the index.
+
+git-diff-tree [-r] <tree-ish-1> <tree-ish-2> [<pattern>...]::
+ compares the trees named by the two arguments.
+
+git-diff-files [<pattern>...]::
+ compares the index and the files on the filesystem.
+
+
+An output line is formatted this way:
+
+------------------------------------------------
+in-place edit :100644 100644 bcd1234... 0123456... M file0
+copy-edit :100644 100644 abcd123... 1234567... C68 file1 file2
+rename-edit :100644 100644 abcd123... 1234567... R86 file1 file3
+create :000000 100644 0000000... 1234567... A file4
+delete :100644 000000 1234567... 0000000... D file5
+unmerged :000000 000000 0000000... 0000000... U file6
+------------------------------------------------
+
+That is, from the left to the right:
+
+. a colon.
+. mode for "src"; 000000 if creation or unmerged.
+. a space.
+. mode for "dst"; 000000 if deletion or unmerged.
+. a space.
+. sha1 for "src"; 0\{40\} if creation or unmerged.
+. a space.
+. sha1 for "dst"; 0\{40\} if creation, unmerged or "look at work tree".
+. a space.
+. status, followed by optional "score" number.
+. a tab or a NUL when '-z' option is used.
+. path for "src"
+. a tab or a NUL when '-z' option is used; only exists for C or R.
+. path for "dst"; only exists for C or R.
+. an LF or a NUL when '-z' option is used, to terminate the record.
+
+<sha1> is shown as all 0's if a file is new on the filesystem
+and it is out of sync with the index.
+
+Example:
+
+------------------------------------------------
+:100644 100644 5be4a4...... 000000...... M file.c
+------------------------------------------------
+
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
+
+Generating patches with -p
+--------------------------
+
+When "git-diff-index", "git-diff-tree", or "git-diff-files" are run
+with a '-p' option, they do not produce the output described above;
+instead they produce a patch file.
+
+The patch generation can be customized at two levels.
+
+1. When the environment variable 'GIT_EXTERNAL_DIFF' is not set,
+ these commands internally invoke "diff" like this:
+
+ diff -L a/<path> -L b/<path> -pu <old> <new>
++
+For added files, `/dev/null` is used for <old>. For removed
+files, `/dev/null` is used for <new>
++
+The "diff" formatting options can be customized via the
+environment variable 'GIT_DIFF_OPTS'. For example, if you
+prefer context diff:
+
+ GIT_DIFF_OPTS=-c git-diff-index -p HEAD
+
+
+2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
+ program named by it is called, instead of the diff invocation
+ described above.
++
+For a path that is added, removed, or modified,
+'GIT_EXTERNAL_DIFF' is called with 7 parameters:
+
+ path old-file old-hex old-mode new-file new-hex new-mode
++
+where:
+
+ <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
+ contents of <old|new>,
+ <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
+ <old|new>-mode:: are the octal representation of the file modes.
+
++
+The file parameters can point at the user's working file
+(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
+when a new file is added), or a temporary file (e.g. `old-file` in the
+index). 'GIT_EXTERNAL_DIFF' should not worry about unlinking the
+temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits.
+
+For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
+parameter, <path>.
+
+
+git specific extension to diff format
+-------------------------------------
+
+What -p option produces is slightly different from the
+traditional diff format.
+
+1. It is preceeded with a "git diff" header, that looks like
+ this:
+
+ diff --git a/file1 b/file2
++
+The `a/` and `b/` filenames are the same unless rename/copy is
+involved. Especially, even for a creation or a deletion,
+`/dev/null` is _not_ used in place of `a/` or `b/` filenames.
++
+When rename/copy is involved, `file1` and `file2` show the
+name of the source file of the rename/copy and the name of
+the file that rename/copy produces, respectively.
+
+2. It is followed by one or more extended header lines:
+
+ old mode <mode>
+ new mode <mode>
+ deleted file mode <mode>
+ new file mode <mode>
+ copy from <path>
+ copy to <path>
+ rename from <path>
+ rename to <path>
+ similarity index <number>
+ dissimilarity index <number>
+ index <hash>..<hash> <mode>
+
+3. TAB, LF, and backslash characters in pathnames are
+ represented as `\t`, `\n`, and `\\`, respectively.
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
new file mode 100644
index 000000000..6b496ede2
--- /dev/null
+++ b/Documentation/diff-options.txt
@@ -0,0 +1,62 @@
+-p::
+ Generate patch (see section on generating patches)
+
+-u::
+ Synonym for "-p".
+
+-z::
+ \0 line termination on output
+
+--name-only::
+ Show only names of changed files.
+
+--name-status::
+ Show only names and status of changed files.
+
+--full-index::
+ Instead of the first handful characters, show full
+ object name of pre- and post-image blob on the "index"
+ line when generating a patch format output.
+
+-B::
+ Break complete rewrite changes into pairs of delete and create.
+
+-M::
+ Detect renames.
+
+-C::
+ Detect copies as well as renames.
+
+--find-copies-harder::
+ For performance reasons, by default, -C option finds copies only
+ if the original file of the copy was modified in the same
+ changeset. This flag makes the command
+ inspect unmodified files as candidates for the source of
+ copy. This is a very expensive operation for large
+ projects, so use it with caution.
+
+-l<num>::
+ -M and -C options require O(n^2) processing time where n
+ is the number of potential rename/copy targets. This
+ option prevents rename/copy detection from running if
+ the number of rename/copy targets exceeds the specified
+ number.
+
+-S<string>::
+ Look for differences that contain the change in <string>.
+
+--pickaxe-all::
+ When -S finds a change, show all the changes in that
+ changeset, not just the files that contain the change
+ in <string>.
+
+-O<orderfile>::
+ Output the patch in the order specified in the
+ <orderfile>, which has one shell glob pattern per line.
+
+-R::
+ Swap two inputs; that is, show differences from index or
+ on-disk file to tree contents.
+
+For more detailed explanation on these common options, see also
+link:diffcore.html[diffcore documentation].
diff --git a/Documentation/diffcore.txt b/Documentation/diffcore.txt
new file mode 100644
index 000000000..cb4e56200
--- /dev/null
+++ b/Documentation/diffcore.txt
@@ -0,0 +1,275 @@
+Tweaking diff output
+====================
+June 2005
+
+
+Introduction
+------------
+
+The diff commands git-diff-index, git-diff-files, git-diff-tree, and
+git-diff-stages can be told to manipulate differences they find in
+unconventional ways before showing diff(1) output. The manipulation
+is collectively called "diffcore transformation". This short note
+describes what they are and how to use them to produce diff outputs
+that are easier to understand than the conventional kind.
+
+
+The chain of operation
+----------------------
+
+The git-diff-* family works by first comparing two sets of
+files:
+
+ - git-diff-index compares contents of a "tree" object and the
+ working directory (when '\--cached' flag is not used) or a
+ "tree" object and the index file (when '\--cached' flag is
+ used);
+
+ - git-diff-files compares contents of the index file and the
+ working directory;
+
+ - git-diff-tree compares contents of two "tree" objects;
+
+ - git-diff-stages compares contents of blobs at two stages in an
+ unmerged index file.
+
+In all of these cases, the commands themselves compare
+corresponding paths in the two sets of files. The result of
+comparison is passed from these commands to what is internally
+called "diffcore", in a format similar to what is output when
+the -p option is not used. E.g.
+
+------------------------------------------------
+in-place edit :100644 100644 bcd1234... 0123456... M file0
+create :000000 100644 0000000... 1234567... A file4
+delete :100644 000000 1234567... 0000000... D file5
+unmerged :000000 000000 0000000... 0000000... U file6
+------------------------------------------------
+
+The diffcore mechanism is fed a list of such comparison results
+(each of which is called "filepair", although at this point each
+of them talks about a single file), and transforms such a list
+into another list. There are currently 6 such transformations:
+
+- diffcore-pathspec
+- diffcore-break
+- diffcore-rename
+- diffcore-merge-broken
+- diffcore-pickaxe
+- diffcore-order
+
+These are applied in sequence. The set of filepairs git-diff-\*
+commands find are used as the input to diffcore-pathspec, and
+the output from diffcore-pathspec is used as the input to the
+next transformation. The final result is then passed to the
+output routine and generates either diff-raw format (see Output
+format sections of the manual for git-diff-\* commands) or
+diff-patch format.
+
+
+diffcore-pathspec: For Ignoring Files Outside Our Consideration
+---------------------------------------------------------------
+
+The first transformation in the chain is diffcore-pathspec, and
+is controlled by giving the pathname parameters to the
+git-diff-* commands on the command line. The pathspec is used
+to limit the world diff operates in. It removes the filepairs
+outside the specified set of pathnames. E.g. If the input set
+of filepairs included:
+
+------------------------------------------------
+:100644 100644 bcd1234... 0123456... M junkfile
+------------------------------------------------
+
+but the command invocation was "git-diff-files myfile", then the
+junkfile entry would be removed from the list because only "myfile"
+is under consideration.
+
+Implementation note. For performance reasons, git-diff-tree
+uses the pathname parameters on the command line to cull set of
+filepairs it feeds the diffcore mechanism itself, and does not
+use diffcore-pathspec, but the end result is the same.
+
+
+diffcore-break: For Splitting Up "Complete Rewrites"
+----------------------------------------------------
+
+The second transformation in the chain is diffcore-break, and is
+controlled by the -B option to the git-diff-* commands. This is
+used to detect a filepair that represents "complete rewrite" and
+break such filepair into two filepairs that represent delete and
+create. E.g. If the input contained this filepair:
+
+------------------------------------------------
+:100644 100644 bcd1234... 0123456... M file0
+------------------------------------------------
+
+and if it detects that the file "file0" is completely rewritten,
+it changes it to:
+
+------------------------------------------------
+:100644 000000 bcd1234... 0000000... D file0
+:000000 100644 0000000... 0123456... A file0
+------------------------------------------------
+
+For the purpose of breaking a filepair, diffcore-break examines
+the extent of changes between the contents of the files before
+and after modification (i.e. the contents that have "bcd1234..."
+and "0123456..." as their SHA1 content ID, in the above
+example). The amount of deletion of original contents and
+insertion of new material are added together, and if it exceeds
+the "break score", the filepair is broken into two. The break
+score defaults to 50% of the size of the smaller of the original
+and the result (i.e. if the edit shrinks the file, the size of
+the result is used; if the edit lengthens the file, the size of
+the original is used), and can be customized by giving a number
+after "-B" option (e.g. "-B75" to tell it to use 75%).
+
+
+diffcore-rename: For Detection Renames and Copies
+-------------------------------------------------
+
+This transformation is used to detect renames and copies, and is
+controlled by the -M option (to detect renames) and the -C option
+(to detect copies as well) to the git-diff-* commands. If the
+input contained these filepairs:
+
+------------------------------------------------
+:100644 000000 0123456... 0000000... D fileX
+:000000 100644 0000000... 0123456... A file0
+------------------------------------------------
+
+and the contents of the deleted file fileX is similar enough to
+the contents of the created file file0, then rename detection
+merges these filepairs and creates:
+
+------------------------------------------------
+:100644 100644 0123456... 0123456... R100 fileX file0
+------------------------------------------------
+
+When the "-C" option is used, the original contents of modified files,
+and deleted files (and also unmodified files, if the
+"\--find-copies-harder" option is used) are considered as candidates
+of the source files in rename/copy operation. If the input were like
+these filepairs, that talk about a modified file fileY and a newly
+created file file0:
+
+------------------------------------------------
+:100644 100644 0123456... 1234567... M fileY
+:000000 100644 0000000... bcd3456... A file0
+------------------------------------------------
+
+the original contents of fileY and the resulting contents of
+file0 are compared, and if they are similar enough, they are
+changed to:
+
+------------------------------------------------
+:100644 100644 0123456... 1234567... M fileY
+:100644 100644 0123456... bcd3456... C100 fileY file0
+------------------------------------------------
+
+In both rename and copy detection, the same "extent of changes"
+algorithm used in diffcore-break is used to determine if two
+files are "similar enough", and can be customized to use
+a similarity score different from the default of 50% by giving a
+number after the "-M" or "-C" option (e.g. "-M8" to tell it to use
+8/10 = 80%).
+
+Note. When the "-C" option is used with `\--find-copies-harder`
+option, git-diff-\* commands feed unmodified filepairs to
+diffcore mechanism as well as modified ones. This lets the copy
+detector consider unmodified files as copy source candidates at
+the expense of making it slower. Without `\--find-copies-harder`,
+git-diff-\* commands can detect copies only if the file that was
+copied happened to have been modified in the same changeset.
+
+
+diffcore-merge-broken: For Putting "Complete Rewrites" Back Together
+--------------------------------------------------------------------
+
+This transformation is used to merge filepairs broken by
+diffcore-break, and not transformed into rename/copy by
+diffcore-rename, back into a single modification. This always
+runs when diffcore-break is used.
+
+For the purpose of merging broken filepairs back, it uses a
+different "extent of changes" computation from the ones used by
+diffcore-break and diffcore-rename. It counts only the deletion
+from the original, and does not count insertion. If you removed
+only 10 lines from a 100-line document, even if you added 910
+new lines to make a new 1000-line document, you did not do a
+complete rewrite. diffcore-break breaks such a case in order to
+help diffcore-rename to consider such filepairs as candidate of
+rename/copy detection, but if filepairs broken that way were not
+matched with other filepairs to create rename/copy, then this
+transformation merges them back into the original
+"modification".
+
+The "extent of changes" parameter can be tweaked from the
+default 80% (that is, unless more than 80% of the original
+material is deleted, the broken pairs are merged back into a
+single modification) by giving a second number to -B option,
+like these:
+
+* -B50/60 (give 50% "break score" to diffcore-break, use 60%
+ for diffcore-merge-broken).
+
+* -B/60 (the same as above, since diffcore-break defaults to 50%).
+
+Note that earlier implementation left a broken pair as a separate
+creation and deletion patches. This was an unnecessary hack and
+the latest implementation always merges all the broken pairs
+back into modifications, but the resulting patch output is
+formatted differently for easier review in case of such
+a complete rewrite by showing the entire contents of old version
+prefixed with '-', followed by the entire contents of new
+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 "original" side has the specified string and
+whose "result" side does not. 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
+changeset easier.
+
+
+diffcore-order: For Sorting the Output Based on Filenames
+---------------------------------------------------------
+
+This is used to reorder the filepairs according to the user's
+(or project's) taste, and is controlled by the -O option to the
+git-diff-* commands.
+
+This takes a text file each of whose lines is a shell glob
+pattern. Filepairs that match a glob pattern on an earlier line
+in the file are output before ones that match a later line, and
+filepairs that do not match any glob pattern are output last.
+
+As an example, a typical orderfile for the core git probably
+would look like this:
+
+------------------------------------------------
+README
+Makefile
+Documentation
+*.h
+*.c
+t
+------------------------------------------------
+
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
new file mode 100644
index 000000000..a25d04a4f
--- /dev/null
+++ b/Documentation/fetch-options.txt
@@ -0,0 +1,19 @@
+-a, \--append::
+ Append ref names and object names of fetched refs to the
+ existing contents of `.git/FETCH_HEAD`. Without this
+ option old data in `.git/FETCH_HEAD` will be overwritten.
+
+-f, \--force::
+
+-t, \--tags::
+ By default, the git core utilities will not fetch and store
+ tags under the same name as the remote repository; ask it
+ to do so using `--tags`. Using this option will bound the
+ list of objects pulled to the remote tags. Commits in branches
+ beyond the tags will be ignored.
+
+-u, \--update-head-ok::
+ By default `git-fetch` refuses to update the head which
+ corresponds to the current branch. This flag disables the
+ check. Note that fetching into the current branch will not
+ update the index and working directory, so use it with care.
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
new file mode 100644
index 000000000..4cae41267
--- /dev/null
+++ b/Documentation/git-add.txt
@@ -0,0 +1,75 @@
+git-add(1)
+==========
+
+NAME
+----
+git-add - Add files to the index file.
+
+SYNOPSIS
+--------
+'git-add' [-n] [-v] <file>...
+
+DESCRIPTION
+-----------
+A simple wrapper for git-update-index to add files to the index,
+for people used to do "cvs add".
+
+
+OPTIONS
+-------
+<file>...::
+ Files to add to the index.
+
+-n::
+ Don't actually add the file(s), just show if they exist.
+
+-v::
+ Be verbose.
+
+
+DISCUSSION
+----------
+
+The list of <file> given to the command is fed to `git-ls-files`
+command to list files that are not registerd in the index and
+are not ignored/excluded by `$GIT_DIR/info/exclude` file or
+`.gitignore` file in each directory. This means two things:
+
+. You can put the name of a directory on the command line, and
+ the command will add all files in it and its subdirectories;
+
+. Giving the name of a file that is already in index does not
+ run `git-update-index` on that path.
+
+
+EXAMPLES
+--------
+git-add Documentation/\\*.txt::
+
+ Adds all `\*.txt` files that are not in the index under
+ `Documentation` directory and its subdirectories.
++
+Note that the asterisk `\*` is quoted from the shell in this
+example; this lets the command to include the files from
+subdirectories of `Documentation/` directory.
+
+git-add git-*.sh::
+
+ Adds all git-*.sh scripts that are not in the index.
+ Because this example lets shell expand the asterisk
+ (i.e. you are listing the files explicitly), it does not
+ add `subdir/git-foo.sh` to the index.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
new file mode 100644
index 000000000..1ceed112f
--- /dev/null
+++ b/Documentation/git-am.txt
@@ -0,0 +1,96 @@
+git-am(1)
+=========
+
+NAME
+----
+git-am - Apply a series of patches in a mailbox
+
+
+SYNOPSIS
+--------
+'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>...
+'git-am' [--skip | --resolved]
+
+DESCRIPTION
+-----------
+Splits mail messages in a mailbox into commit log message,
+authorship information and patches, and applies them to the
+current branch.
+
+OPTIONS
+-------
+--signoff::
+ Add `Signed-off-by:` line to the commit message, using
+ the committer identity of yourself.
+
+--dotest=<dir>::
+ Instead of `.dotest` directory, use <dir> as a working
+ area to store extracted patches.
+
+--utf8, --keep::
+ Pass `--utf8` and `--keep` flags to `git-mailinfo` (see
+ gitlink:git-mailinfo[1]).
+
+--binary::
+ Pass `--allow-binary-replacement` flag to `git-apply`
+ (see gitlink:git-apply[1]).
+
+--3way::
+ When the patch does not apply cleanly, fall back on
+ 3-way merge, if the patch records the identity of blobs
+ it is supposed to apply to, and we have those blobs
+ locally.
+
+--skip::
+ Skip the current patch. This is only meaningful when
+ restarting an aborted patch.
+
+--interactive::
+ Run interactively, just like git-applymbox.
+
+--resolved::
+ After a patch failure (e.g. attempting to apply
+ conflicting patch), the user has applied it by hand and
+ the index file stores the result of the application.
+ Make a commit using the authorship and commit log
+ extracted from the e-mail message and the current index
+ file, and continue.
+
+DISCUSSION
+----------
+
+When initially invoking it, you give it names of the mailboxes
+to crunch. Upon seeing the first patch that does not apply, it
+aborts in the middle, just like 'git-applymbox' does. You can
+recover from this in one of two ways:
+
+. skip the current one by re-running the command with '--skip'
+ option.
+
+. hand resolve the conflict in the working directory, and update
+ the index file to bring it in a state that the patch should
+ have produced. Then run the command with '--resume' option.
+
+The command refuses to process new mailboxes while `.dotest`
+directory exists, so if you decide to start over from scratch,
+run `rm -f .dotest` before running the command with mailbox
+names.
+
+
+SEE ALSO
+--------
+gitlink:git-applymbox[1], gitlink:git-applypatch[1].
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
new file mode 100644
index 000000000..626e28159
--- /dev/null
+++ b/Documentation/git-apply.txt
@@ -0,0 +1,104 @@
+git-apply(1)
+============
+
+NAME
+----
+git-apply - Apply patch on a git index file and a work tree
+
+
+SYNOPSIS
+--------
+'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [<patch>...]
+
+DESCRIPTION
+-----------
+Reads supplied diff output and applies it on a git index file
+and a work tree.
+
+OPTIONS
+-------
+<patch>...::
+ The files to read patch from. '-' can be used to read
+ from the standard input.
+
+--stat::
+ Instead of applying the patch, output diffstat for the
+ input. Turns off "apply".
+
+--numstat::
+ Similar to \--stat, but shows number of added and
+ deleted lines in decimal notation and pathname without
+ abbreviation, to make it more machine friendly. Turns
+ off "apply".
+
+--summary::
+ Instead of applying the patch, output a condensed
+ summary of information obtained from git diff extended
+ headers, such as creations, renames and mode changes.
+ Turns off "apply".
+
+--check::
+ Instead of applying the patch, see if the patch is
+ applicable to the current work tree and/or the index
+ file and detects errors. Turns off "apply".
+
+--index::
+ When --check is in effect, or when applying the patch
+ (which is the default when none of the options that
+ disables it is in effect), make sure the patch is
+ applicable to what the current index file records. If
+ the file to be patched in the work tree is not
+ up-to-date, it is flagged as an error. This flag also
+ causes the index file to be updated.
+
+--index-info::
+ Newer git-diff output has embedded 'index information'
+ for each blob to help identify the original version that
+ the patch applies to. When this flag is given, and if
+ the original version of the blob is available locally,
+ outputs information about them to the standard output.
+
+-z::
+ When showing the index information, do not munge paths,
+ but use NUL terminated machine readable format. Without
+ this flag, the pathnames output will have TAB, LF, and
+ backslash characters replaced with `\t`, `\n`, and `\\`,
+ respectively.
+
+--apply::
+ If you use any of the options marked ``Turns off
+ "apply"'' above, git-apply reads and outputs the
+ information you asked without actually applying the
+ patch. Give this flag after those flags to also apply
+ the patch.
+
+--no-add::
+ When applying a patch, ignore additions made by the
+ patch. This can be used to extract common part between
+ two files by first running `diff` on them and applying
+ the result with this option, which would apply the
+ deletion part but not addition part.
+
+--allow-binary-replacement::
+ When applying a patch, which is a git-enhanced patch
+ that was prepared to record the pre- and post-image object
+ name in full, and the path being patched exactly matches
+ the object the patch applies to (i.e. "index" line's
+ pre-image object name is what is in the working tree),
+ and the post-image object is available in the object
+ database, use the post-image object as the patch
+ result. This allows binary files to be patched in a
+ very limited way.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt
new file mode 100644
index 000000000..f74c6a49b
--- /dev/null
+++ b/Documentation/git-applymbox.txt
@@ -0,0 +1,92 @@
+git-applymbox(1)
+================
+
+NAME
+----
+git-applymbox - Apply a series of patches in a mailbox
+
+
+SYNOPSIS
+--------
+'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
+
+DESCRIPTION
+-----------
+Splits mail messages in a mailbox into commit log message,
+authorship information and patches, and applies them to the
+current branch.
+
+
+OPTIONS
+-------
+-q::
+ Apply patches interactively. The user will be given
+ opportunity to edit the log message and the patch before
+ attempting to apply it.
+
+-k::
+ Usually the program 'cleans up' the Subject: header line
+ to extract the title line for the commit log message,
+ among which (1) remove 'Re:' or 're:', (2) leading
+ whitespaces, (3) '[' up to ']', typically '[PATCH]', and
+ then prepends "[PATCH] ". This flag forbids this
+ munging, and is most useful when used to read back 'git
+ format-patch --mbox' output.
+
+-m::
+ Patches are applied with `git-apply` command, and unless
+ it cleanly applies without fuzz, the processing fails.
+ With this flag, if a tree that the patch applies cleanly
+ is found in a repository, the patch is applied to the
+ tree and then a 3-way merge between the resulting tree
+ and the current tree.
+
+-u::
+ By default, the commit log message, author name and
+ author email are taken from the e-mail without any
+ charset conversion, after minimally decoding MIME
+ transfer encoding. This flag causes the resulting
+ commit to be encoded in utf-8 by transliterating them.
+ Note that the patch is always used as is without charset
+ conversion, even with this flag.
+
+-c .dotest/<num>::
+ When the patch contained in an e-mail does not cleanly
+ apply, the command exits with an error message. The
+ patch and extracted message are found in .dotest/, and
+ you could re-run 'git applymbox' with '-c .dotest/<num>'
+ flag to restart the process after inspecting and fixing
+ them.
+
+<mbox>::
+ The name of the file that contains the e-mail messages
+ with patches. This file should be in the UNIX mailbox
+ format. See 'SubmittingPatches' document to learn about
+ the formatting convention for e-mail submission.
+
+<signoff>::
+ The name of the file that contains your "Signed-off-by"
+ line. See 'SubmittingPatches' document to learn what
+ "Signed-off-by" line means. You can also just say
+ 'yes', 'true', 'me', or 'please' to use an automatically
+ generated "Signed-off-by" line based on your committer
+ identity.
+
+
+SEE ALSO
+--------
+gitlink:git-am[1], gitlink:git-applypatch[1].
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-applypatch.txt b/Documentation/git-applypatch.txt
new file mode 100644
index 000000000..5b9037de9
--- /dev/null
+++ b/Documentation/git-applypatch.txt
@@ -0,0 +1,50 @@
+git-applypatch(1)
+=================
+
+NAME
+----
+git-applypatch - Apply one patch extracted from an e-mail.
+
+
+SYNOPSIS
+--------
+'git-applypatch' <msg> <patch> <info> [<signoff>]
+
+DESCRIPTION
+-----------
+Takes three files <msg>, <patch>, and <info> prepared from an
+e-mail message by 'git-mailinfo', and creates a commit. It is
+usually not necessary to use this command directly.
+
+This command can run `applypatch-msg`, `pre-applypatch`, and
+`post-applypatch` hooks. See link:hooks.html[hooks] for more
+information.
+
+
+OPTIONS
+-------
+<msg>::
+ Commit log message (sans the first line, which comes
+ from e-mail Subject stored in <info>).
+
+<patch>::
+ The patch to apply.
+
+<info>::
+ Author and subject information extracted from e-mail,
+ used on "author" line and as the first line of the
+ commit log message.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt
new file mode 100644
index 000000000..fcda0125a
--- /dev/null
+++ b/Documentation/git-archimport.txt
@@ -0,0 +1,85 @@
+git-archimport(1)
+=================
+
+NAME
+----
+git-archimport - Import an Arch repository into git
+
+
+SYNOPSIS
+--------
+`git-archimport` [ -h ] [ -v ] [ -T ] [ -t tempdir ]
+ <archive/branch> [ <archive/branch> ]
+
+DESCRIPTION
+-----------
+Imports a project from one or more Arch repositories. It will follow branches
+and repositories within the namespaces defined by the <archive/branch>
+parameters suppplied. If it cannot find the remote branch a merge comes from
+it will just import it as a regular commit. If it can find it, it will mark it
+as a merge whenever possible (see discussion below).
+
+The script expects you to provide the key roots where it can start the import
+from an 'initial import' or 'tag' type of Arch commit. It will follow and
+import new branches within the provided roots.
+
+It expects to be dealing with one project only. If it sees
+branches that have different roots, it will refuse to run. In that case,
+edit your <archive/branch> parameters to define clearly the scope of the
+import.
+
+`git-archimport` uses `tla` extensively in the background to access the
+Arch repository.
+Make sure you have a recent version of `tla` available in the path. `tla` must
+know about the repositories you pass to `git-archimport`.
+
+For the initial import `git-archimport` expects to find itself in an empty
+directory. To follow the development of a project that uses Arch, rerun
+`git-archimport` with the same parameters as the initial import to perform
+incremental imports.
+
+MERGES
+------
+Patch merge data from Arch is used to mark merges in git as well. git
+does not care much about tracking patches, and only considers a merge when a
+branch incorporates all the commits since the point they forked. The end result
+is that git will have a good idea of how far branches have diverged. So the
+import process does lose some patch-trading metadata.
+
+Fortunately, when you try and merge branches imported from Arch,
+git will find a good merge base, and it has a good chance of identifying
+patches that have been traded out-of-sequence between the branches.
+
+OPTIONS
+-------
+
+-h::
+ Display usage.
+
+-v::
+ Verbose output.
+
+-T::
+ Many tags. Will create a tag for every commit, reflecting the commit
+ name in the Arch repository.
+
+-t <tmpdir>::
+ Override the default tempdir.
+
+
+<archive/branch>::
+ Archive/branch identifier in a format that `tla log` understands.
+
+
+Author
+------
+Written by Martin Langhoff <martin@catalyst.net.nz>.
+
+Documentation
+--------------
+Documentation by Junio C Hamano, Martin Langhoff and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
new file mode 100644
index 000000000..39fa665d9
--- /dev/null
+++ b/Documentation/git-bisect.txt
@@ -0,0 +1,100 @@
+git-bisect(1)
+=============
+
+NAME
+----
+git-bisect - Find the change that introduced a bug
+
+
+SYNOPSIS
+--------
+'git bisect' start
+'git bisect' bad <rev>
+'git bisect' good <rev>
+'git bisect' reset [<branch>]
+'git bisect' visualize
+'git bisect' replay <logfile>
+'git bisect' log
+
+DESCRIPTION
+-----------
+This command uses 'git-rev-list --bisect' option to help drive
+the binary search process to find which change introduced a bug,
+given an old "good" commit object name and a later "bad" commit
+object name.
+
+The way you use it is:
+
+------------------------------------------------
+git bisect start
+git bisect bad # Current version is bad
+git bisect good v2.6.13-rc2 # v2.6.13-rc2 was the last version
+ # tested that was good
+------------------------------------------------
+
+When you give at least one bad and one good versions, it will
+bisect the revision tree and say something like:
+
+------------------------------------------------
+Bisecting: 675 revisions left to test after this
+------------------------------------------------
+
+and check out the state in the middle. Now, compile that kernel, and boot
+it. Now, let's say that this booted kernel works fine, then just do
+
+------------------------------------------------
+git bisect good # this one is good
+------------------------------------------------
+
+which will now say
+
+------------------------------------------------
+Bisecting: 337 revisions left to test after this
+------------------------------------------------
+
+and you continue along, compiling that one, testing it, and depending on
+whether it is good or bad, you say "git bisect good" or "git bisect bad",
+and ask for the next bisection.
+
+Until you have no more left, and you'll have been left with the first bad
+kernel rev in "refs/bisect/bad".
+
+Oh, and then after you want to reset to the original head, do a
+
+------------------------------------------------
+git bisect reset
+------------------------------------------------
+
+to get back to the master branch, instead of being in one of the bisection
+branches ("git bisect start" will do that for you too, actually: it will
+reset the bisection state, and before it does that it checks that you're
+not using some old bisection branch).
+
+During the bisection process, you can say
+
+ git bisect visualize
+
+to see the currently remaining suspects in `gitk`.
+
+The good/bad input is logged, and `git bisect
+log` shows what you have done so far. You can truncate its
+output somewhere and save it in a file, and run
+
+ git bisect replay that-file
+
+if you find later you made a mistake telling good/bad about a
+revision.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+-------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
new file mode 100644
index 000000000..98014f6d9
--- /dev/null
+++ b/Documentation/git-branch.txt
@@ -0,0 +1,46 @@
+git-branch(1)
+=============
+
+NAME
+----
+git-branch - Create a new branch, or remove an old one.
+
+SYNOPSIS
+--------
+'git-branch' [-d | -D] [<branchname> [start-point]]
+
+DESCRIPTION
+-----------
+If no argument is provided, show available branches and mark current
+branch with star. Otherwise, create a new branch of name <branchname>.
+
+If a starting point is also specified, that will be where the branch is
+created, otherwise it will be created at the current HEAD.
+
+OPTIONS
+-------
+-d::
+ Delete a branch. The branch must be fully merged.
+
+-D::
+ Delete a branch irrespective of its index status.
+
+<branchname>::
+ The name of the branch to create or delete.
+
+start-point::
+ Where to create the branch; defaults to HEAD. This
+ option has no meaning with -d and -D.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt
new file mode 100644
index 000000000..ab4dcae21
--- /dev/null
+++ b/Documentation/git-cat-file.txt
@@ -0,0 +1,60 @@
+git-cat-file(1)
+===============
+
+NAME
+----
+git-cat-file - Provide content or type information for repository objects
+
+
+SYNOPSIS
+--------
+'git-cat-file' (-t | -s | <type>) <object>
+
+DESCRIPTION
+-----------
+Provides content or type of objects in the repository. The type
+is required unless '-t' is used to find the object type,
+or '-s' is used to find the object size.
+
+OPTIONS
+-------
+<object>::
+ The sha1 identifier of the object.
+
+-t::
+ Instead of the content, show the object type identified by
+ <object>.
+
+-s::
+ Instead of the content, show the object size identified by
+ <object>.
+
+<type>::
+ Typically this matches the real type of <object> but asking
+ for a type that can trivially be dereferenced from the given
+ <object> is also permitted. An example is to ask for a
+ "tree" with <object> being a commit object that contains it,
+ or to ask for a "blob" with <object> being a tag object that
+ points at it.
+
+OUTPUT
+------
+If '-t' is specified, one of the <type>. If '-s' is specified,
+the size of the <object> in bytes.
+
+Otherwise the raw (though uncompressed) contents of the <object> will
+be returned.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt
new file mode 100644
index 000000000..636e9516b
--- /dev/null
+++ b/Documentation/git-check-ref-format.txt
@@ -0,0 +1,50 @@
+git-check-ref-format(1)
+=======================
+
+NAME
+----
+git-check-ref-format - Make sure ref name is well formed.
+
+SYNOPSIS
+--------
+'git-check-ref-format' <refname>
+
+DESCRIPTION
+-----------
+Checks if a given 'refname' is acceptable, and exits non-zero if
+it is not.
+
+A reference is used in git to specify branches and tags. A
+branch head is stored under `$GIT_DIR/refs/heads` directory, and
+a tag is stored under `$GIT_DIR/refs/tags` directory. git
+imposes the following rules on how refs are named:
+
+. It could be named hierarchically (i.e. separated with slash
+ `/`), but each of its component cannot begin with a dot `.`;
+
+. It cannot have two consecutive dots `..` anywhere;
+
+. It cannot have ASCII control character (i.e. bytes whose
+ values are lower than \040, or \177 `DEL`), space, tilde `~`,
+ caret `{caret}`, or colon `:` anywhere;
+
+. It cannot end with a slash `/`.
+
+These rules makes it easy for shell script based tools to parse
+refnames, and also avoids ambiguities in certain refname
+expressions (see gitlink:git-rev-parse[1]). Namely:
+
+. double-dot `..` are often used as in `ref1..ref2`, and in some
+ context this notation means `{caret}ref1 ref2` (i.e. not in
+ ref1 and in ref2).
+
+. tilde `~` and caret `{caret}` are used to introduce postfix
+ 'nth parent' and 'peel onion' operation.
+
+. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
+ value and store it in dstref" in fetch and push operations.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt
new file mode 100644
index 000000000..5bff4865f
--- /dev/null
+++ b/Documentation/git-checkout-index.txt
@@ -0,0 +1,99 @@
+git-checkout-index(1)
+=====================
+
+NAME
+----
+git-checkout-index - Copy files from the index to the working directory
+
+
+SYNOPSIS
+--------
+'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
+ [--] <file>...
+
+DESCRIPTION
+-----------
+Will copy all files listed from the index to the working directory
+(not overwriting existing files).
+
+OPTIONS
+-------
+-u|--index::
+ update stat information for the checked out entries in
+ the index file.
+
+-q|--quiet::
+ be quiet if files exist or are not in the index
+
+-f|--force::
+ forces overwrite of existing files
+
+-a|--all::
+ checks out all files in the index. Cannot be used
+ together with explicit filenames.
+
+-n|--no-create::
+ Don't checkout new files, only refresh files already checked
+ out.
+
+--prefix=<string>::
+ When creating files, prepend <string> (usually a directory
+ including a trailing /)
+
+--::
+ Do not interpret any more arguments as options.
+
+The order of the flags used to matter, but not anymore.
+
+Just doing "git-checkout-index" does nothing. You probably meant
+"git-checkout-index -a". And if you want to force it, you want
+"git-checkout-index -f -a".
+
+Intuitiveness is not the goal here. Repeatability is. The reason for
+the "no arguments means no work" thing is that from scripts you are
+supposed to be able to do things like:
+
+ find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
+
+which will force all existing `*.h` files to be replaced with their
+cached copies. If an empty command line implied "all", then this would
+force-refresh everything in the index, which was not the point.
+
+To update and refresh only the files already checked out:
+
+ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
+
+Oh, and the "--" is just a good idea when you know the rest will be
+filenames. Just so that you wouldn't have a filename of "-a" causing
+problems (not possible in the above example, but get used to it in
+scripting!).
+
+The prefix ability basically makes it trivial to use
+git-checkout-index as an "export as tree" function. Just read the
+desired tree into the index, and do a
+
+ git-checkout-index --prefix=git-export-dir/ -a
+
+and git-checkout-index will "export" the index into the specified
+directory.
+
+NOTE The final "/" is important. The exported name is literally just
+prefixed with the specified string, so you can also do something like
+
+ git-checkout-index --prefix=.merged- Makefile
+
+to check out the currently cached copy of `Makefile` into the file
+`.merged-Makefile`
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
new file mode 100644
index 000000000..b7bb1b4c7
--- /dev/null
+++ b/Documentation/git-checkout.txt
@@ -0,0 +1,79 @@
+git-checkout(1)
+===============
+
+NAME
+----
+git-checkout - Checkout and switch to a branch.
+
+SYNOPSIS
+--------
+'git-checkout' [-f] [-b <new_branch>] [<branch>] [<paths>...]
+
+DESCRIPTION
+-----------
+
+When <paths> are not given, this command switches branches, by
+updating the index and working tree to reflect the specified
+branch, <branch>, and updating HEAD to be <branch> or, if
+specified, <new_branch>.
+
+When <paths> are given, this command does *not* switch
+branches. It updates the named paths in the working tree from
+the index file (i.e. it runs `git-checkout-index -f -u`). In
+this case, `-f` and `-b` options are meaningless and giving
+either of them results in an error. <branch> argument can be
+used to specify a specific tree-ish to update the index for the
+given paths before updating the working tree.
+
+
+OPTIONS
+-------
+-f::
+ Force an re-read of everything.
+
+-b::
+ Create a new branch and start it at <branch>.
+
+<new_branch>::
+ Name for the new branch.
+
+<branch>::
+ Branch to checkout; may be any object ID that resolves to a
+ commit. Defaults to HEAD.
+
+
+EXAMPLE
+-------
+
+The following sequence checks out the `master` branch, reverts
+the `Makefile` to two revisions back, deletes hello.c by
+mistake, and gets it back from the index.
+
+------------
+$ git checkout master
+$ git checkout master~2 Makefile
+$ rm -f hello.c
+$ git checkout hello.c
+------------
+
+If you have an unfortunate branch that is named `hello.c`, the
+last step above would be confused as an instruction to switch to
+that branch. You should instead write:
+
+------------
+$ git checkout -- hello.c
+------------
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt
new file mode 100644
index 000000000..26e046779
--- /dev/null
+++ b/Documentation/git-cherry-pick.txt
@@ -0,0 +1,56 @@
+git-cherry-pick(1)
+==================
+
+NAME
+----
+git-cherry-pick - Apply the change introduced by an existing commit.
+
+SYNOPSIS
+--------
+'git-cherry-pick' [-n] [-r] <commit>
+
+DESCRIPTION
+-----------
+Given one existing commit, apply the change the patch introduces, and record a
+new commit that records it. This requires your working tree to be clean (no
+modifications from the HEAD commit).
+
+OPTIONS
+-------
+<commit>::
+ Commit to cherry-pick.
+
+-r::
+ Usually the command appends which commit was
+ cherry-picked after the original commit message when
+ making a commit. This option, '--replay', causes it to
+ use the original commit message intact. This is useful
+ when you are reordering the patches in your private tree
+ before publishing, and is used by 'git rebase'.
+
+-n::
+ Usually the command automatically creates a commit with
+ a commit log message stating which commit was
+ cherry-picked. This flag applies the change necessary
+ to cherry-pick the named commit to your working tree,
+ but does not make the commit. In addition, when this
+ option is used, your working tree does not have to match
+ the HEAD commit. The cherry-pick is done against the
+ beginning state of your working tree.
++
+This is useful when cherry-picking more than one commits'
+effect to your working tree in a row.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt
new file mode 100644
index 000000000..af87966e5
--- /dev/null
+++ b/Documentation/git-cherry.txt
@@ -0,0 +1,42 @@
+git-cherry(1)
+=============
+
+NAME
+----
+git-cherry - Find commits not merged upstream.
+
+SYNOPSIS
+--------
+'git-cherry' [-v] <upstream> [<head>]
+
+DESCRIPTION
+-----------
+Each commit between the fork-point and <head> is examined, and compared against
+the change each commit between the fork-point and <upstream> introduces.
+Commits already included in upstream are prefixed with '-' (meaning "drop from
+my local pull"), while commits missing from upstream are prefixed with '+'
+(meaning "add to the updated upstream").
+
+OPTIONS
+-------
+-v::
+ Verbose.
+
+<upstream>::
+ Upstream branch to compare against.
+
+<head>::
+ Working branch; defaults to HEAD.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-clone-pack.txt b/Documentation/git-clone-pack.txt
new file mode 100644
index 000000000..cfc7b62f3
--- /dev/null
+++ b/Documentation/git-clone-pack.txt
@@ -0,0 +1,60 @@
+git-clone-pack(1)
+=================
+
+NAME
+----
+git-clone-pack - Clones a repository by receiving packed objects.
+
+
+SYNOPSIS
+--------
+'git-clone-pack' [--exec=<git-upload-pack>] [<host>:]<directory> [<head>...]
+
+DESCRIPTION
+-----------
+Clones a repository into the current repository by invoking
+'git-upload-pack', possibly on the remote host via ssh, in
+the named repository, and stores the sent pack in the local
+repository.
+
+OPTIONS
+-------
+--exec=<git-upload-pack>::
+ Use this to specify the path to 'git-upload-pack' on the
+ remote side, if it is not found on your $PATH.
+ Installations of sshd ignore the user's environment
+ setup scripts for login shells (e.g. .bash_profile) and
+ your privately installed git may not be found on the system
+ default $PATH. Another workaround suggested is to set
+ up your $PATH in ".bashrc", but this flag is for people
+ who do not want to pay the overhead for non-interactive
+ shells by having a lean .bashrc file (they set most of
+ the things up in .bash_profile).
+
+<host>::
+ A remote host that houses the repository. When this
+ part is specified, 'git-upload-pack' is invoked via
+ ssh.
+
+<directory>::
+ The repository to sync from.
+
+<head>...::
+ The heads to update. This is relative to $GIT_DIR
+ (e.g. "HEAD", "refs/heads/master"). When unspecified,
+ all heads are updated to match the remote repository.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
new file mode 100644
index 000000000..83f58ae53
--- /dev/null
+++ b/Documentation/git-clone.txt
@@ -0,0 +1,89 @@
+git-clone(1)
+============
+
+NAME
+----
+git-clone - Clones a repository.
+
+
+SYNOPSIS
+--------
+'git-clone' [-l [-s]] [-q] [-n] [-u <upload-pack>] <repository> [<directory>]
+
+DESCRIPTION
+-----------
+Clones a repository into a newly created directory. All remote
+branch heads are copied under `$GIT_DIR/refs/heads/`, except
+that the remote `master` is also copied to `origin` branch.
+
+In addition, `$GIT_DIR/remotes/origin` file is set up to have
+this line:
+
+ Pull: master:origin
+
+This is to help the typical workflow of working off of the
+remote `master` branch. Every time `git pull` without argument
+is run, the progress on the remote `master` branch is tracked by
+copying it into the local `origin` branch, and merged into the
+branch you are currently working on. Remote branches other than
+`master` are also added there to be tracked.
+
+
+OPTIONS
+-------
+--local::
+-l::
+ When the repository to clone from is on a local machine,
+ this flag bypasses normal "git aware" transport
+ mechanism and clones the repository by making a copy of
+ HEAD and everything under objects and refs directories.
+ The files under .git/objects/ directory are hardlinked
+ to save space when possible.
+
+--shared::
+-s::
+ When the repository to clone is on the local machine,
+ instead of using hard links, automatically setup
+ .git/objects/info/alternatives to share the objects
+ with the source repository. The resulting repository
+ starts out without any object of its own.
+
+--quiet::
+-q::
+ Operate quietly. This flag is passed to "rsync" and
+ "git-clone-pack" commands when given.
+
+-n::
+ No checkout of HEAD is performed after the clone is complete.
+
+--upload-pack <upload-pack>::
+-u <upload-pack>::
+ When given, and the repository to clone from is handled
+ by 'git-clone-pack', '--exec=<upload-pack>' is passed to
+ the command to specify non-default path for the command
+ run on the other end.
+
+<repository>::
+ The (possibly remote) repository to clone from. It can
+ be any URL git-fetch supports.
+
+<directory>::
+ The name of a new directory to clone into. The "humanish"
+ part of the source repository is used if no directory is
+ explicitly given ("repo" for "/path/to/repo.git" and "foo"
+ for "host.xz:foo/.git"). Cloning into an existing directory
+ is not allowed.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt
new file mode 100644
index 000000000..a794192d7
--- /dev/null
+++ b/Documentation/git-commit-tree.txt
@@ -0,0 +1,99 @@
+git-commit-tree(1)
+==================
+
+NAME
+----
+git-commit-tree - Creates a new commit object
+
+
+SYNOPSIS
+--------
+'git-commit-tree' <tree> [-p <parent commit>]\* < changelog
+
+DESCRIPTION
+-----------
+Creates a new commit object based on the provided tree object and
+emits the new commit object id on stdout. If no parent is given then
+it is considered to be an initial tree.
+
+A commit object usually has 1 parent (a commit after a change) or up
+to 16 parents. More than one parent represents a merge of branches
+that led to them.
+
+While a tree represents a particular directory state of a working
+directory, a commit represents that state in "time", and explains how
+to get there.
+
+Normally a commit would identify a new "HEAD" state, and while git
+doesn't care where you save the note about that state, in practice we
+tend to just write the result to the file that is pointed at by
+`.git/HEAD`, so that we can always see what the last committed
+state was.
+
+OPTIONS
+-------
+<tree>::
+ An existing tree object
+
+-p <parent commit>::
+ Each '-p' indicates the id of a parent commit object.
+
+
+Commit Information
+------------------
+
+A commit encapsulates:
+
+- all parent object ids
+- author name, email and date
+- committer name and email and the commit time.
+
+If not provided, "git-commit-tree" uses your name, hostname and domain to
+provide author and committer info. This can be overridden by
+either `.git/config` file, or using the following environment variables.
+
+ GIT_AUTHOR_NAME
+ GIT_AUTHOR_EMAIL
+ GIT_AUTHOR_DATE
+ GIT_COMMITTER_NAME
+ GIT_COMMITTER_EMAIL
+
+(nb "<", ">" and "\n"s are stripped)
+
+In `.git/config` file, the following items are used:
+
+ [user]
+ name = "Your Name"
+ email = "your@email.address.xz"
+
+A commit comment is read from stdin (max 999 chars). If a changelog
+entry is not provided via "<" redirection, "git-commit-tree" will just wait
+for one to be entered and terminated with ^D.
+
+
+Diagnostics
+-----------
+You don't exist. Go away!::
+ The passwd(5) gecos field couldn't be read
+Your parents must have hated you!::
+ The password(5) gecos field is longer than a giant static buffer.
+Your sysadmin must hate you!::
+ The password(5) name field is longer than a giant static buffer.
+
+See Also
+--------
+gitlink:git-write-tree[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt
new file mode 100644
index 000000000..1edc278c6
--- /dev/null
+++ b/Documentation/git-commit.txt
@@ -0,0 +1,71 @@
+git-commit(1)
+=============
+
+NAME
+----
+git-commit - Record your changes
+
+SYNOPSIS
+--------
+'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg>] [-e] <file>...
+
+DESCRIPTION
+-----------
+Updates the index file for given paths, or all modified files if
+'-a' is specified, and makes a commit object. The command
+VISUAL and EDITOR environment variables to edit the commit log
+message.
+
+This command can run `commit-msg`, `pre-commit`, and
+`post-commit` hooks. See link:hooks.html[hooks] for more
+information.
+
+OPTIONS
+-------
+-a::
+ Update all paths in the index file.
+
+-c or -C <commit>::
+ Take existing commit object, and reuse the log message
+ and the authorship information (including the timestamp)
+ when creating the commit. With '-C', the editor is not
+ invoked; with '-c' the user can further edit the commit
+ message.
+
+-F <file>::
+ Take the commit message from the given file. Use '-' to
+ read the message from the standard input.
+
+-m <msg>::
+ Use the given <msg> as the commit message.
+
+-s::
+ Add Signed-off-by line at the end of the commit message.
+
+-v::
+ Look for suspicious lines the commit introduces, and
+ abort committing if there is one. The definition of
+ 'suspicious lines' is currently the lines that has
+ trailing whitespaces, and the lines whose indentation
+ has a SP character immediately followed by a TAB
+ character.
+
+-e::
+ The message taken from file with `-F`, command line with
+ `-m`, and from file with `-C` are usually used as the
+ commit log message unmodified. This option lets you
+ further edit the message taken from these sources.
+
+<file>...::
+ Update specified paths in the index file before committing.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-convert-objects.txt b/Documentation/git-convert-objects.txt
new file mode 100644
index 000000000..b1220c06e
--- /dev/null
+++ b/Documentation/git-convert-objects.txt
@@ -0,0 +1,29 @@
+git-convert-objects(1)
+======================
+
+NAME
+----
+git-convert-objects - Converts old-style git repository
+
+
+SYNOPSIS
+--------
+'git-convert-objects'
+
+DESCRIPTION
+-----------
+Converts old-style git repository to the latest format
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt
new file mode 100644
index 000000000..36888d98b
--- /dev/null
+++ b/Documentation/git-count-objects.txt
@@ -0,0 +1,28 @@
+git-count-objects(1)
+====================
+
+NAME
+----
+git-count-objects - Reports on unpacked objects.
+
+SYNOPSIS
+--------
+'git-count-objects'
+
+DESCRIPTION
+-----------
+This counts the number of unpacked object files and disk space consumed by
+them, to help you decide when it is a good time to repack.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt
new file mode 100644
index 000000000..c3da73d1c
--- /dev/null
+++ b/Documentation/git-cvsexportcommit.txt
@@ -0,0 +1,56 @@
+git-cvsexportcommit(1)
+======================
+
+NAME
+----
+git-cvsexportcommit - Export a commit to a CVS checkout
+
+
+SYNOPSIS
+--------
+git-cvsapplycommmit.perl
+ [ -h ] [ -v ] [ -c ] [ -p ] [PARENTCOMMIT] COMMITID
+
+
+DESCRIPTION
+-----------
+Exports a commit from GIT to a CVS checkout, making it easier
+to merge patches from a git repository into a CVS repository.
+
+Execute it from the root of the CVS working copy. GIT_DIR must be defined.
+
+It does its best to do the safe thing, it will check that the files are
+unchanged and up to date in the CVS checkout, and it will not autocommit
+by default.
+
+Supports file additions, removals, and commits that affect binary files.
+
+If the commit is a merge commit, you must tell git-cvsapplycommit what parent
+should the changeset be done against.
+
+OPTIONS
+-------
+
+-c::
+ Commit automatically if the patch applied cleanly. It will not
+ commit if any hunks fail to apply or there were other problems.
+
+-p::
+ Be pedantic (paranoid) when applying patches. Invokes patch with
+ --fuzz=0
+
+-v::
+ Verbose.
+
+Author
+------
+Written by Martin Langhoff <martin@catalyst.net.nz>
+
+Documentation
+--------------
+Documentation by Martin Langhoff <martin@catalyst.net.nz>
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt
new file mode 100644
index 000000000..88bd3b0f1
--- /dev/null
+++ b/Documentation/git-cvsimport.txt
@@ -0,0 +1,112 @@
+git-cvsimport(1)
+================
+
+NAME
+----
+git-cvsimport - Import a CVS repository into git
+
+
+SYNOPSIS
+--------
+'git-cvsimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ]
+ [ -d <CVSROOT> ] [ -p <options-for-cvsps> ]
+ [ -C <git_repository> ] [ -i ] [ -P <file> ] [ -k ]
+ [ -s <subst> ] [ -m ] [ -M regex ] [ <CVS_module> ]
+
+
+DESCRIPTION
+-----------
+Imports a CVS repository into git. It will either create a new
+repository, or incrementally import into an existing one.
+
+Splitting the CVS log into patch sets is done by 'cvsps'.
+At least version 2.1 is required.
+
+OPTIONS
+-------
+-d <CVSROOT>::
+ The root of the CVS archive. May be local (a simple path) or remote;
+ currently, only the :local:, :ext: and :pserver: access methods
+ are supported.
+
+-C <target-dir>::
+ The git repository to import to. If the directory doesn't
+ exist, it will be created. Default is the current directory.
+
+-i::
+ Import-only: don't perform a checkout after importing. This option
+ ensures the working directory and index remain untouched and will
+ not create them if they do not exist.
+
+-k::
+ Kill keywords: will extract files with -kk from the CVS archive
+ to avoid noisy changesets. Highly recommended, but off by default
+ to preserve compatibility with early imported trees.
+
+-u::
+ Convert underscores in tag and branch names to dots.
+
+-o <branch-for-HEAD>::
+ The 'HEAD' branch from CVS is imported to the 'origin' branch within
+ the git repository, as 'HEAD' already has a special meaning for git.
+ Use this option if you want to import into a different branch.
++
+Use '-o master' for continuing an import that was initially done by
+the old cvs2git tool.
+
+-p <options-for-cvsps>::
+ Additional options for cvsps.
+ The options '-u' and '-A' are implicit and should not be used here.
++
+If you need to pass multiple options, separate them with a comma.
+
+-P:: <cvsps-output-file>
+ Instead of calling cvsps, read the provided cvsps output file. Useful
+ for debugging or when cvsps is being handled outside cvsimport.
+
+-m::
+ Attempt to detect merges based on the commit message. This option
+ will enable default regexes that try to capture the name source
+ branch name from the commit message.
+
+-M <regex>::
+ Attempt to detect merges based on the commit message with a custom
+ regex. It can be used with -m to also see the default regexes.
+ You must escape forward slashes.
+
+-v::
+ Verbosity: let 'cvsimport' report what it is doing.
+
+<CVS_module>::
+ The CVS module you want to import. Relative to <CVSROOT>.
+
+-h::
+ Print a short usage message and exit.
+
+-z <fuzz>::
+ Pass the timestamp fuzz factor to cvsps.
+
+-s <subst>::
+ Substitute the character "/" in branch names with <subst>
+
+OUTPUT
+------
+If '-v' is specified, the script reports what it is doing.
+
+Otherwise, success is indicated the Unix way, i.e. by simply exiting with
+a zero exit status.
+
+
+Author
+------
+Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
+various participants of the git-list <git@vger.kernel.org>.
+
+Documentation
+--------------
+Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt
new file mode 100644
index 000000000..378385830
--- /dev/null
+++ b/Documentation/git-daemon.txt
@@ -0,0 +1,71 @@
+git-daemon(1)
+=============
+
+NAME
+----
+git-daemon - A really simple server for git repositories.
+
+SYNOPSIS
+--------
+'git-daemon' [--verbose] [--syslog] [--inetd | --port=n] [--export-all]
+ [--timeout=n] [--init-timeout=n] [directory...]
+
+DESCRIPTION
+-----------
+A really simple TCP git daemon that normally listens on port "DEFAULT_GIT_PORT"
+aka 9418. It waits for a connection, and will just execute "git-upload-pack"
+when it gets one.
+
+It's careful in that there's a magic request-line that gives the command and
+what directory to upload, and it verifies that the directory is ok.
+
+It verifies that the directory has the magic file "git-daemon-export-ok", and
+it will refuse to export any git directory that hasn't explicitly been marked
+for export this way (unless the '--export-all' parameter is specified). If you
+pass some directory paths as 'git-daemon' arguments, you can further restrict
+the offers to a whitelist comprising of those.
+
+This is ideally suited for read-only updates, ie pulling from git repositories.
+
+OPTIONS
+-------
+--export-all::
+ Allow pulling from all directories that look like GIT repositories
+ (have the 'objects' subdirectory and a 'HEAD' file), even if they
+ do not have the 'git-daemon-export-ok' file.
+
+--inetd::
+ Have the server run as an inetd service. Implies --syslog.
+
+--port::
+ Listen on an alternative port.
+
+--init-timeout::
+ Timeout between the moment the connection is established and the
+ client request is received (typically a rather low value, since
+ that should be basically immediate).
+
+--timeout::
+ Timeout for specific client sub-requests. This includes the time
+ it takes for the server to process the sub-request and time spent
+ waiting for next client's request.
+
+--syslog::
+ Log to syslog instead of stderr. Note that this option does not imply
+ --verbose, thus by default only error conditions will be logged.
+
+--verbose::
+ Log details about the incoming connections and requested files.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt
new file mode 100644
index 000000000..3b04bfeec
--- /dev/null
+++ b/Documentation/git-diff-files.txt
@@ -0,0 +1,43 @@
+git-diff-files(1)
+=================
+
+NAME
+----
+git-diff-files - Compares files in the working tree and the index
+
+
+SYNOPSIS
+--------
+'git-diff-files' [-q] [<common diff options>] [<path>...]
+
+DESCRIPTION
+-----------
+Compares the files in the working tree and the index. When paths
+are specified, compares only those named paths. Otherwise all
+entries in the index are compared. The output format is the
+same as "git-diff-index" and "git-diff-tree".
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+-q::
+ Remain silent even on nonexisting files
+
+Output format
+-------------
+include::diff-format.txt[]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt
new file mode 100644
index 000000000..dba6d30fc
--- /dev/null
+++ b/Documentation/git-diff-index.txt
@@ -0,0 +1,133 @@
+git-diff-index(1)
+=================
+
+NAME
+----
+git-diff-index - Compares content and mode of blobs between the index and repository
+
+
+SYNOPSIS
+--------
+'git-diff-index' [-m] [--cached] [<common diff options>] <tree-ish> [<path>...]
+
+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.
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<tree-ish>::
+ The id of a tree object to diff against.
+
+--cached::
+ do not consider the on-disk file at all
+
+-m::
+ By default, files recorded in the index but not checked
+ out are reported as deleted. This flag makes
+ "git-diff-index" say that all non-checked-out files are up
+ to date.
+
+Output format
+-------------
+include::diff-format.txt[]
+
+Operating Modes
+---------------
+You can choose whether you want to trust the index file entirely
+(using the '--cached' flag) or ask the diff logic to show any files
+that don't match the stat state as being "tentatively changed". Both
+of these operations are very useful indeed.
+
+Cached Mode
+-----------
+If '--cached' is specified, it allows you to ask:
+
+ show me the differences between HEAD and the current index
+ contents (the ones I'd write with a "git-write-tree")
+
+For example, let's say that you have worked on your working directory, updated
+some files in the index and are ready to commit. You want to see eactly
+*what* you are going to commit is without having to write a new tree
+object and compare it that way, and to do that, you just do
+
+ git-diff-index --cached HEAD
+
+Example: let's say I had renamed `commit.c` to `git-commit.c`, and I had
+done an "git-update-index" to make that effective in the index file.
+"git-diff-files" wouldn't show anything at all, since the index file
+matches my working directory. But doing a "git-diff-index" does:
+
+ torvalds@ppc970:~/git> git-diff-index --cached HEAD
+ -100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 commit.c
+ +100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 git-commit.c
+
+You can trivially see that the above is a rename.
+
+In fact, "git-diff-index --cached" *should* always be entirely equivalent to
+actually doing a "git-write-tree" and comparing that. Except this one is much
+nicer for the case where you just want to check where you are.
+
+So doing a "git-diff-index --cached" is basically very useful when you are
+asking yourself "what have I already marked for being committed, and
+what's the difference to a previous tree".
+
+Non-cached Mode
+---------------
+The "non-cached" mode takes a different approach, and is potentially
+the more useful of the two in that what it does can't be emulated with
+a "git-write-tree" + "git-diff-tree". Thus that's the default mode.
+The non-cached version asks the question:
+
+ show me the differences between HEAD and the currently checked out
+ tree - index contents _and_ files that aren't up-to-date
+
+which is obviously a very useful question too, since that tells you what
+you *could* commit. Again, the output matches the "git-diff-tree -r"
+output to a tee, but with a twist.
+
+The twist is that if some file doesn't match the index, we don't have
+a backing store thing for it, and we use the magic "all-zero" sha1 to
+show that. So let's say that you have edited `kernel/sched.c`, but
+have not actually done a "git-update-index" on it yet - there is no
+"object" associated with the new state, and you get:
+
+ torvalds@ppc970:~/v2.6/linux> git-diff-index HEAD
+ *100644->100664 blob 7476bb......->000000...... kernel/sched.c
+
+ie it shows that the tree has changed, and that `kernel/sched.c` has is
+not up-to-date and may contain new stuff. The all-zero sha1 means that to
+get the real diff, you need to look at the object in the working directory
+directly rather than do an object-to-object diff.
+
+NOTE: As with other commands of this type, "git-diff-index" does not
+actually look at the contents of the file at all. So maybe
+`kernel/sched.c` hasn't actually changed, and it's just that you
+touched it. In either case, it's a note that you need to
+"git-upate-index" it to make the index be in sync.
+
+NOTE: You can have a mixture of files show up as "has been updated"
+and "is still dirty in the working directory" together. You can always
+tell which file is in which state, since the "has been updated" ones
+show a valid sha1, and the "not in sync with the index" ones will
+always have the special all-zero sha1.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-diff-stages.txt b/Documentation/git-diff-stages.txt
new file mode 100644
index 000000000..28c60fc7e
--- /dev/null
+++ b/Documentation/git-diff-stages.txt
@@ -0,0 +1,40 @@
+git-diff-stages(1)
+==================
+
+NAME
+----
+git-diff-stages - Compares content and mode of blobs between stages in an unmerged index file.
+
+
+SYNOPSIS
+--------
+'git-diff-stages' [<common diff options>] <stage1> <stage2> [<path>...]
+
+DESCRIPTION
+-----------
+Compares the content and mode of the blobs in two stages in an
+unmerged index file.
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<stage1>,<stage2>::
+ The stage number to be compared.
+
+Output format
+-------------
+include::diff-format.txt[]
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt
new file mode 100644
index 000000000..9a2947e27
--- /dev/null
+++ b/Documentation/git-diff-tree.txt
@@ -0,0 +1,141 @@
+git-diff-tree(1)
+================
+
+NAME
+----
+git-diff-tree - Compares the content and mode of blobs found via two tree objects
+
+
+SYNOPSIS
+--------
+'git-diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty] [-t] [-r] [--root] [<common diff options>] <tree-ish> [<tree-ish>] [<path>...]
+
+DESCRIPTION
+-----------
+Compares the content and mode of the blobs found via two tree objects.
+
+If there is only one <tree-ish> given, the commit is compared with its parents
+(see --stdin below).
+
+Note that "git-diff-tree" can use the tree encapsulated in a commit object.
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<tree-ish>::
+ The id of a tree object.
+
+<path>...::
+ If provided, the results are limited to a subset of files
+ matching one of these prefix strings.
+ ie file matches `/^<pattern1>|<pattern2>|.../`
+ Note that this parameter does not provide any wildcard or regexp
+ features.
+
+-r::
+ recurse into sub-trees
+
+-t::
+ show tree entry itself as well as subtrees. Implies -r.
+
+--root::
+ When '--root' is specified the initial commit will be showed as a big
+ creation event. This is equivalent to a diff against the NULL tree.
+
+--stdin::
+ When '--stdin' is specified, the command does not take
+ <tree-ish> arguments from the command line. Instead, it
+ reads either one <commit> or a pair of <tree-ish>
+ separated with a single space from its standard input.
++
+When a single commit is given on one line of such input, it compares
+the commit with its parents. The following flags further affects its
+behaviour. This does not apply to the case where two <tree-ish>
+separated with a single space are given.
+
+-m::
+ By default, "git-diff-tree --stdin" does not show
+ differences for merge commits. With this flag, it shows
+ differences to that commit from all of its parents.
+
+-s::
+ By default, "git-diff-tree --stdin" shows differences,
+ either in machine-readable form (without '-p') or in patch
+ form (with '-p'). This output can be supressed. It is
+ only useful with '-v' flag.
+
+-v::
+ This flag causes "git-diff-tree --stdin" to also show
+ the commit message before the differences.
+
+--pretty[=(raw|medium|short)]::
+ This is used to control "pretty printing" format of the
+ commit message. Without "=<style>", it defaults to
+ medium.
+
+--no-commit-id::
+ git-diff-tree outputs a line with the commit ID when
+ applicable. This flag suppressed the commit ID output.
+
+
+Limiting Output
+---------------
+If you're only interested in differences in a subset of files, for
+example some architecture-specific files, you might do:
+
+ git-diff-tree -r <tree-ish> <tree-ish> arch/ia64 include/asm-ia64
+
+and it will only show you what changed in those two directories.
+
+Or if you are searching for what changed in just `kernel/sched.c`, just do
+
+ git-diff-tree -r <tree-ish> <tree-ish> kernel/sched.c
+
+and it will ignore all differences to other files.
+
+The pattern is always the prefix, and is matched exactly. There are no
+wildcards. Even stricter, it has to match a complete path component.
+I.e. "foo" does not pick up `foobar.h`. "foo" does match `foo/bar.h`
+so it can be used to name subdirectories.
+
+An example of normal usage is:
+
+ torvalds@ppc970:~/git> git-diff-tree 5319e4......
+ *100664->100664 blob ac348b.......->a01513....... git-fsck-objects.c
+
+which tells you that the last commit changed just one file (it's from
+this one:
+
+-----------------------------------------------------------------------------
+commit 3c6f7ca19ad4043e9e72fa94106f352897e651a8
+tree 5319e4d609cdd282069cc4dce33c1db559539b03
+parent b4e628ea30d5ab3606119d2ea5caeab141d38df7
+author Linus Torvalds <torvalds@ppc970.osdl.org> Sat Apr 9 12:02:30 2005
+committer Linus Torvalds <torvalds@ppc970.osdl.org> Sat Apr 9 12:02:30 2005
+
+Make "git-fsck-objects" print out all the root commits it finds.
+
+Once I do the reference tracking, I'll also make it print out all the
+HEAD commits it finds, which is even more interesting.
+-----------------------------------------------------------------------------
+
+in case you care).
+
+Output format
+-------------
+include::diff-format.txt[]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt
new file mode 100644
index 000000000..cadaf5945
--- /dev/null
+++ b/Documentation/git-diff.txt
@@ -0,0 +1,52 @@
+git-diff(1)
+===========
+
+NAME
+----
+git-diff - Show changes between commits, commit and working tree, etc.
+
+
+SYNOPSIS
+--------
+'git-diff' [ --diff-options ] <ent>{0,2} [<path>...]
+
+DESCRIPTION
+-----------
+Show changes between two ents, an ent and the working tree, an
+ent and the index file, or the index file and the working tree.
+The combination of what is compared with what is determined by
+the number of ents given to the command.
+
+`----------------`--------`-----------------------------`------------------
+Number of ents Options What's Compared Underlying command
+---------------------------------------------------------------------------
+0 - index file and working tree git-diff-files
+1 --cached ent and index file git-diff-index
+1 - ent and working tree git-diff-index
+2 - two ents git-diff-tree
+---------------------------------------------------------------------------
+
+OPTIONS
+-------
+--diff-options::
+ '--diff-options' are passed to the `git-diff-files`,
+ `git-diff-index`, and `git-diff-tree` commands. See the
+ documentation for these commands for description.
+
+<path>...::
+ The <path> arguments are also passed to `git-diff-\*`
+ commands.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt
new file mode 100644
index 000000000..ea6faab05
--- /dev/null
+++ b/Documentation/git-fetch-pack.txt
@@ -0,0 +1,68 @@
+git-fetch-pack(1)
+=================
+
+NAME
+----
+git-fetch-pack - Receive missing objects from another repository.
+
+
+SYNOPSIS
+--------
+git-fetch-pack [-q] [--exec=<git-upload-pack>] [<host>:]<directory> [<refs>...]
+
+DESCRIPTION
+-----------
+Invokes 'git-upload-pack' on a potentially remote repository,
+and asks it to send objects missing from this repository, to
+update the named heads. The list of commits available locally
+is found out by scanning local $GIT_DIR/refs/ and sent to
+'git-upload-pack' running on the other end.
+
+This command degenerates to download everything to complete the
+asked refs from the remote side when the local side does not
+have a common ancestor commit.
+
+
+OPTIONS
+-------
+-q::
+ Pass '-q' flag to 'git-unpack-objects'; this makes the
+ cloning process less verbose.
+
+--exec=<git-upload-pack>::
+ Use this to specify the path to 'git-upload-pack' on the
+ remote side, if is not found on your $PATH.
+ Installations of sshd ignores the user's environment
+ setup scripts for login shells (e.g. .bash_profile) and
+ your privately installed git may not be found on the system
+ default $PATH. Another workaround suggested is to set
+ up your $PATH in ".bashrc", but this flag is for people
+ who do not want to pay the overhead for non-interactive
+ shells by having a lean .bashrc file (they set most of
+ the things up in .bash_profile).
+
+<host>::
+ A remote host that houses the repository. When this
+ part is specified, 'git-upload-pack' is invoked via
+ ssh.
+
+<directory>::
+ The repository to sync from.
+
+<refs>...::
+ The remote heads to update from. This is relative to
+ $GIT_DIR (e.g. "HEAD", "refs/heads/master"). When
+ unspecified, update from all heads the remote side has.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt
new file mode 100644
index 000000000..438240c0c
--- /dev/null
+++ b/Documentation/git-fetch.txt
@@ -0,0 +1,48 @@
+git-fetch(1)
+============
+
+NAME
+----
+git-fetch - Download objects and a head from another repository.
+
+
+SYNOPSIS
+--------
+'git-fetch' <options> <repository> <refspec>...
+
+
+DESCRIPTION
+-----------
+Fetches named heads or tags from another repository, along with
+the objects necessary to complete them.
+
+The ref names and their object names of fetched refs are stored
+in `.git/FETCH_HEAD`. This information is left for a later merge
+operation done by "git resolve" or "git octopus".
+
+
+OPTIONS
+-------
+include::fetch-options.txt[]
+
+include::pull-fetch-param.txt[]
+
+
+
+SEE ALSO
+--------
+gitlink:git-pull[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+Documentation
+-------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt
new file mode 100644
index 000000000..a70eb3994
--- /dev/null
+++ b/Documentation/git-fmt-merge-msg.txt
@@ -0,0 +1,39 @@
+git-fmt-merge-msg(1)
+====================
+
+NAME
+----
+git-fmt-merge-msg - Produce a merge commit message
+
+
+SYNOPSIS
+--------
+'git-fmt-merge-msg' <$GIT_DIR/FETCH_HEAD
+
+DESCRIPTION
+-----------
+Takes the list of merged objects on stdin and produces a suitable
+commit message to be used for the merge commit, usually to be
+passed as the '<merge-message>' argument of `git-merge`.
+
+This script is intended mostly for internal use by scripts
+automatically invoking `git-merge`.
+
+
+SEE ALSO
+--------
+gitlink:git-merge[1]
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
new file mode 100644
index 000000000..7a3abec02
--- /dev/null
+++ b/Documentation/git-format-patch.txt
@@ -0,0 +1,93 @@
+git-format-patch(1)
+===================
+
+NAME
+----
+git-format-patch - Prepare patches for e-mail submission.
+
+
+SYNOPSIS
+--------
+'git-format-patch' [-n][-o <dir>|--stdout][-k][--mbox][--diff-options] <his> [<mine>]
+
+DESCRIPTION
+-----------
+Prepare each commit with its patch since <mine> head forked from
+<his> head, one file per patch, for e-mail submission. Each
+output file is numbered sequentially from 1, and uses the first
+line of the commit message (massaged for pathname safety) as the
+filename.
+
+When -o is specified, output files are created in that
+directory; otherwise in the current working directory.
+
+When -n is specified, instead of "[PATCH] Subject", the first
+line is formatted as "[PATCH N/M] Subject", unless you have only
+one patch.
+
+When --mbox is specified, the output is formatted to resemble
+UNIX mailbox format, and can be concatenated together for
+processing with applymbox.
+
+
+OPTIONS
+-------
+-o <dir>::
+ Use <dir> to store the resulting files, instead of the
+ current working directory.
+
+-n::
+ Name output in '[PATCH n/m]' format.
+
+-k::
+ Do not strip/add '[PATCH]' from the first line of the
+ commit log message.
+
+--author, --date::
+ Output From: and Date: headers for commits made by
+ yourself as well. Usually these are output only for
+ commits made by people other than yourself.
+
+--mbox::
+ Format the output files for closer to mbox format by
+ adding a phony Unix "From " line, so they can be
+ concatenated together and fed to `git-applymbox`.
+ Implies --author and --date.
+
+--stdout::
+ This flag generates the mbox formatted output to the
+ standard output, instead of saving them into a file per
+ patch and implies --mbox.
+
+
+EXAMPLES
+--------
+
+git-format-patch -k --stdout R1..R2 | git-am -3 -k::
+ Extract commits between revisions R1 and R2, and apply
+ them on top of the current branch using `git-am` to
+ cherry-pick them.
+
+git-format-patch origin::
+ Extract commits the current branch accumulated since it
+ pulled from origin the last time in a patch form for
+ e-mail submission.
+
+
+See Also
+--------
+gitlink:git-am[1], gitlink:git-send-email
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-fsck-objects.txt b/Documentation/git-fsck-objects.txt
new file mode 100644
index 000000000..bab1f6080
--- /dev/null
+++ b/Documentation/git-fsck-objects.txt
@@ -0,0 +1,144 @@
+git-fsck-objects(1)
+===================
+
+NAME
+----
+git-fsck-objects - Verifies the connectivity and validity of the objects in the database
+
+
+SYNOPSIS
+--------
+'git-fsck-objects' [--tags] [--root] [--unreachable] [--cache] [--standalone | --full] [--strict] [<object>*]
+
+DESCRIPTION
+-----------
+Verifies the connectivity and validity of the objects in the database.
+
+OPTIONS
+-------
+<object>::
+ An object to treat as the head of an unreachability trace.
++
+If no objects are given, git-fsck-objects defaults to using the
+index file and all SHA1 references in .git/refs/* as heads.
+
+--unreachable::
+ Print out objects that exist but that aren't readable from any
+ of the reference nodes.
+
+--root::
+ Report root nodes.
+
+--tags::
+ Report tags.
+
+--cache::
+ Consider any object recorded in the index also as a head node for
+ an unreachability trace.
+
+--standalone::
+ Limit checks to the contents of GIT_OBJECT_DIRECTORY
+ ($GIT_DIR/objects), making sure that it is consistent and
+ complete without referring to objects found in alternate
+ object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
+ nor packed git archives found in $GIT_DIR/objects/pack;
+ cannot be used with --full.
+
+--full::
+ Check not just objects in GIT_OBJECT_DIRECTORY
+ ($GIT_DIR/objects), but also the ones found in alternate
+ object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
+ and in packed git archives found in $GIT_DIR/objects/pack
+ and corresponding pack subdirectories in alternate
+ object pools; cannot be used with --standalone.
+
+--strict::
+ Enable more strict checking, namely to catch a file mode
+ recorded with g+w bit set, which was created by older
+ versions of git. Existing repositories, including the
+ Linux kernel, git itself, and sparse repository have old
+ objects that triggers this check, but it is recommended
+ to check new projects with this flag.
+
+It tests SHA1 and general object sanity, and it does full tracking of
+the resulting reachability and everything else. It prints out any
+corruption it finds (missing or bad objects), and if you use the
+'--unreachable' flag it will also print out objects that exist but
+that aren't readable from any of the specified head nodes.
+
+So for example
+
+ git-fsck-objects --unreachable HEAD $(cat .git/refs/heads/*)
+
+will do quite a _lot_ of verification on the tree. There are a few
+extra validity tests to be added (make sure that tree objects are
+sorted properly etc), but on the whole if "git-fsck-objects" is happy, you
+do have a valid tree.
+
+Any corrupt objects you will have to find in backups or other archives
+(ie you can just remove them and do an "rsync" with some other site in
+the hopes that somebody else has the object you have corrupted).
+
+Of course, "valid tree" doesn't mean that it wasn't generated by some
+evil person, and the end result might be crap. git is a revision
+tracking system, not a quality assurance system ;)
+
+Extracted Diagnostics
+---------------------
+
+expect dangling commits - potential heads - due to lack of head information::
+ You haven't specified any nodes as heads so it won't be
+ possible to differentiate between un-parented commits and
+ root nodes.
+
+missing sha1 directory '<dir>'::
+ The directory holding the sha1 objects is missing.
+
+unreachable <type> <object>::
+ The <type> object <object>, isn't actually referred to directly
+ or indirectly in any of the trees or commits seen. This can
+ mean that there's another root node that you're not specifying
+ or that the tree is corrupt. If you haven't missed a root node
+ then you might as well delete unreachable nodes since they
+ can't be used.
+
+missing <type> <object>::
+ The <type> object <object>, is referred to but isn't present in
+ the database.
+
+dangling <type> <object>::
+ The <type> object <object>, is present in the database but never
+ 'directly' used. A dangling commit could be a root node.
+
+warning: git-fsck-objects: tree <tree> has full pathnames in it::
+ And it shouldn't...
+
+sha1 mismatch <object>::
+ The database has an object who's sha1 doesn't match the
+ database value.
+ This indicates a serious data integrity problem.
+
+Environment Variables
+---------------------
+
+GIT_OBJECT_DIRECTORY::
+ used to specify the object database root (usually $GIT_DIR/objects)
+
+GIT_INDEX_FILE::
+ used to specify the index file of the index
+
+GIT_ALTERNATE_OBJECT_DIRECTORIES::
+ used to specify additional object database roots (usually unset)
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-get-tar-commit-id.txt b/Documentation/git-get-tar-commit-id.txt
new file mode 100644
index 000000000..30b1fbf6e
--- /dev/null
+++ b/Documentation/git-get-tar-commit-id.txt
@@ -0,0 +1,37 @@
+git-get-tar-commit-id(1)
+========================
+
+NAME
+----
+git-get-tar-commit-id - Extract commit ID from an archive created using git-tar-tree.
+
+
+SYNOPSIS
+--------
+'git-get-tar-commit-id' < <tarfile>
+
+
+DESCRIPTION
+-----------
+Acts as a filter, extracting the commit ID stored in archives created by
+git-tar-tree. It reads only the first 1024 bytes of input, thus its
+runtime is not influenced by the size of <tarfile> very much.
+
+If no commit ID is found, git-get-tar-commit-id quietly exists with a
+return code of 1. This can happen if <tarfile> had not been created
+using git-tar-tree or if the first parameter of git-tar-tree had been
+a tree ID instead of a commit ID or tag.
+
+
+Author
+------
+Written by Rene Scharfe <rene.scharfe@lsrfire.ath.cx>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
new file mode 100644
index 000000000..017579348
--- /dev/null
+++ b/Documentation/git-grep.txt
@@ -0,0 +1,46 @@
+git-grep(1)
+===========
+
+NAME
+----
+git-grep - print lines matching a pattern
+
+
+SYNOPSIS
+--------
+'git-grep' <option>... <pattern> <path>...
+
+DESCRIPTION
+-----------
+Searches list of files `git-ls-files` produces for lines
+containing a match to the given pattern.
+
+
+OPTIONS
+-------
+<option>...::
+ Either an option to pass to `grep` or `git-ls-files`.
+ Some `grep` options, such as `-C` and `-m`, that take
+ parameters are known to `git-grep`.
+
+<pattern>::
+ The pattern to look for.
+
+<path>...::
+
+ Optional paths to limit the set of files to be searched;
+ passed to `git-ls-files`.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt
new file mode 100644
index 000000000..07d2c427c
--- /dev/null
+++ b/Documentation/git-hash-object.txt
@@ -0,0 +1,43 @@
+git-hash-object(1)
+==================
+
+NAME
+----
+git-hash-object - Computes object ID and optionally creates a blob from a file.
+
+
+SYNOPSIS
+--------
+'git-hash-object' [-t <type>] [-w] <any-file-on-the-filesystem>
+
+DESCRIPTION
+-----------
+Computes the object ID value for an object with specified type
+with the contents of the named file (which can be outside of the
+work tree), and optionally writes the resulting object into the
+object database. Reports its object ID to its standard output.
+This is used by "git-cvsimport" to update the index
+without modifying files in the work tree. When <type> is not
+specified, it defaults to "blob".
+
+OPTIONS
+-------
+
+-t <type>::
+ Specify the type (default: "blob").
+
+-w::
+ Actually write the object into the object database.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt
new file mode 100644
index 000000000..088624f6c
--- /dev/null
+++ b/Documentation/git-http-fetch.txt
@@ -0,0 +1,41 @@
+git-http-fetch(1)
+=================
+
+NAME
+----
+git-http-fetch - Downloads a remote git repository via HTTP
+
+
+SYNOPSIS
+--------
+'git-http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
+
+DESCRIPTION
+-----------
+Downloads a remote git repository via HTTP.
+
+-c::
+ Get the commit objects.
+-t::
+ Get trees associated with the commit objects.
+-a::
+ Get all the objects.
+-v::
+ Report what is downloaded.
+
+-w <filename>::
+ Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on
+ the local end after the transfer is complete.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt
new file mode 100644
index 000000000..c7066d66f
--- /dev/null
+++ b/Documentation/git-http-push.txt
@@ -0,0 +1,89 @@
+git-http-push(1)
+================
+
+NAME
+----
+git-http-push - Push missing objects using HTTP/DAV.
+
+
+SYNOPSIS
+--------
+'git-http-push' [--complete] [--force] [--verbose] <url> <ref> [<ref>...]
+
+DESCRIPTION
+-----------
+Sends missing objects to remote repository, and updates the
+remote branch.
+
+
+OPTIONS
+-------
+--complete::
+ Do not assume that the remote repository is complete in its
+ current state, and verify all objects in the entire local
+ ref's history exist in the remote repository.
+
+--force::
+ Usually, the command refuses to update a remote ref that
+ is not an ancestor of the local ref used to overwrite it.
+ This flag disables the check. What this means is that
+ the remote repository can lose commits; use it with
+ care.
+
+--verbose::
+ Report the list of objects being walked locally and the
+ list of objects successfully sent to the remote repository.
+
+<ref>...:
+ The remote refs to update.
+
+
+Specifying the Refs
+-------------------
+
+A '<ref>' specification can be either a single pattern, or a pair
+of such patterns separated by a colon ":" (this means that a ref name
+cannot have a colon in it). A single pattern '<name>' is just a
+shorthand for '<name>:<name>'.
+
+Each pattern pair consists of the source side (before the colon)
+and the destination side (after the colon). The ref to be
+pushed is determined by finding a match that matches the source
+side, and where it is pushed is determined by using the
+destination side.
+
+ - It is an error if <src> does not match exactly one of the
+ local refs.
+
+ - If <dst> does not match any remote ref, either
+
+ * it has to start with "refs/"; <dst> is used as the
+ destination literally in this case.
+
+ * <src> == <dst> and the ref that matched the <src> must not
+ exist in the set of remote refs; the ref matched <src>
+ locally is used as the name of the destination.
+
+Without '--force', the <src> ref is stored at the remote only if
+<dst> does not exist, or <dst> is a proper subset (i.e. an
+ancestor) of <src>. This check, known as "fast forward check",
+is performed in order to avoid accidentally overwriting the
+remote ref and lose other peoples' commits from there.
+
+With '--force', the fast forward check is disabled for all refs.
+
+Optionally, a <ref> parameter can be prefixed with a plus '+' sign
+to disable the fast-forward check only on that ref.
+
+
+Author
+------
+Written by Nick Hengeveld <nickh@reactrix.com>
+
+Documentation
+--------------
+Documentation by Nick Hengeveld
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt
new file mode 100644
index 000000000..71ce55727
--- /dev/null
+++ b/Documentation/git-index-pack.txt
@@ -0,0 +1,44 @@
+git-index-pack(1)
+=================
+
+NAME
+----
+git-index-pack - Build pack index file for an existing packed archive
+
+
+SYNOPSIS
+--------
+'git-index-pack' [-o <index-file>] <pack-file>
+
+
+DESCRIPTION
+-----------
+Reads a packed archive (.pack) from the specified file, and
+builds a pack index file (.idx) for it. The packed archive
+together with the pack index can then be placed in the
+objects/pack/ directory of a git repository.
+
+
+OPTIONS
+-------
+-o <index-file>::
+ Write the generated pack index into the specified
+ file. Without this option the name of pack index
+ file is constructed from the name of packed archive
+ file by replacing .pack with .idx (and the program
+ fails if the name of packed archive does not end
+ with .pack).
+
+
+Author
+------
+Written by Sergey Vlasov <vsu@altlinux.ru>
+
+Documentation
+-------------
+Documentation by Sergey Vlasov
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt
new file mode 100644
index 000000000..ef1826ae6
--- /dev/null
+++ b/Documentation/git-init-db.txt
@@ -0,0 +1,40 @@
+git-init-db(1)
+==============
+
+NAME
+----
+git-init-db - Creates an empty git repository
+
+
+SYNOPSIS
+--------
+'git-init-db'
+
+DESCRIPTION
+-----------
+This simply creates an empty git repository - basically a `.git` directory
+and `.git/object/??/`, `.git/refs/heads` and `.git/refs/tags` directories,
+and links `.git/HEAD` symbolically to `.git/refs/heads/master`.
+
+If the 'GIT_DIR' environment variable is set then it specifies a path
+to use instead of `./.git` for the base of the repository.
+
+If the object storage directory is specified via the 'GIT_OBJECT_DIRECTORY'
+environment variable then the sha1 directories are created underneath -
+otherwise the default `$GIT_DIR/objects` directory is used.
+
+"git-init-db" won't hurt an existing repository.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-local-fetch.txt b/Documentation/git-local-fetch.txt
new file mode 100644
index 000000000..87abec1c4
--- /dev/null
+++ b/Documentation/git-local-fetch.txt
@@ -0,0 +1,43 @@
+git-local-fetch(1)
+==================
+
+NAME
+----
+git-local-fetch - Duplicates another git repository on a local system
+
+
+SYNOPSIS
+--------
+'git-local-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n] commit-id path
+
+DESCRIPTION
+-----------
+Duplicates another git repository on a local system.
+
+OPTIONS
+-------
+-c::
+ Get the commit objects.
+-t::
+ Get trees associated with the commit objects.
+-a::
+ Get all the objects.
+-v::
+ Report what is downloaded.
+
+-w <filename>::
+ Writes the commit-id into the filename under $GIT_DIR/refs/<filename> on
+ the local end after the transfer is complete.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
new file mode 100644
index 000000000..e995d1b74
--- /dev/null
+++ b/Documentation/git-log.txt
@@ -0,0 +1,62 @@
+git-log(1)
+==========
+
+NAME
+----
+git-log - Show commit logs
+
+
+SYNOPSIS
+--------
+'git-log' <option>...
+
+DESCRIPTION
+-----------
+Shows the commit logs. This command internally invokes
+'git-rev-list', and the command line options are passed to that
+command.
+
+This manual page describes only the most frequently used options.
+
+OPTIONS
+-------
+--pretty=<format>::
+ Controls the way the commit log is formatted.
+
+--max-count=<n>::
+ Limits the number of commits to show.
+
+<since>..<until>::
+ Show only commits between the named two commits.
+
+
+Examples
+--------
+git log --no-merges::
+
+ Show the whole commit history, but skip any merges
+
+git log v2.6.12.. include/scsi drivers/scsi::
+
+ Show all commits since version 'v2.6.12' that changed any file
+ in the include/scsi or drivers/scsi subdirectories
+
+git log --since="2 weeks ago" -- gitk::
+
+ Show the changes during the last two weeks to the file 'gitk'.
+ The "--" is necessary to avoid confusion with the *branch* named
+ 'gitk'
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-lost-found.txt b/Documentation/git-lost-found.txt
new file mode 100644
index 000000000..03156f218
--- /dev/null
+++ b/Documentation/git-lost-found.txt
@@ -0,0 +1,78 @@
+git-lost-found(1)
+=================
+
+NAME
+----
+git-lost-found - Recover lost refs that luckily have not yet been pruned.
+
+SYNOPSIS
+--------
+'git-lost-found'
+
+DESCRIPTION
+-----------
+Finds dangling commits and tags from the object database, and
+creates refs to them in .git/lost-found/ directory. Commits and
+tags that dereference to commits go to .git/lost-found/commit
+and others are stored in .git/lost-found/other directory.
+
+
+OUTPUT
+------
+One line description from the commit and tag found along with
+their object name are printed on the standard output.
+
+
+EXAMPLE
+-------
+
+Suppose you run 'git tag -f' and mistyped the tag to overwrite.
+The ref to your tag is overwritten, but until you run 'git
+prune', it is still there.
+
+------------
+$ git lost-found
+[1ef2b196d909eed523d4f3c9bf54b78cdd6843c6] GIT 0.99.9c
+...
+------------
+
+Also you can use gitk to browse how they relate to each other
+and existing (probably old) tags.
+
+------------
+$ gitk $(cd .git/lost-found/commit && echo ??*)
+------------
+
+After making sure that it is the object you are looking for, you
+can reconnect it to your regular .git/refs hierarchy.
+
+------------
+$ git cat-file -t 1ef2b196
+tag
+$ git cat-file tag 1ef2b196
+object fa41bbce8e38c67a218415de6cfa510c7e50032a
+type commit
+tag v0.99.9c
+tagger Junio C Hamano <junkio@cox.net> 1131059594 -0800
+
+GIT 0.99.9c
+
+This contains the following changes from the "master" branch, since
+...
+$ git update-ref refs/tags/not-lost-anymore 1ef2b196
+$ git rev-parse not-lost-anymore
+1ef2b196d909eed523d4f3c9bf54b78cdd6843c6
+------------
+
+Author
+------
+Written by Junio C Hamano 濱野 純 <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
new file mode 100644
index 000000000..2f308ecda
--- /dev/null
+++ b/Documentation/git-ls-files.txt
@@ -0,0 +1,213 @@
+git-ls-files(1)
+===============
+
+NAME
+----
+git-ls-files - Information about files in the index/working directory
+
+
+SYNOPSIS
+--------
+'git-ls-files' [-z] [-t]
+ (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])\*
+ (-[c|d|o|i|s|u|k|m])\*
+ [-x <pattern>|--exclude=<pattern>]
+ [-X <file>|--exclude-from=<file>]
+ [--exclude-per-directory=<file>] [--] [<file>]\*
+
+DESCRIPTION
+-----------
+This merges the file listing in the directory cache index with the
+actual working directory list, and shows different combinations of the
+two.
+
+One or more of the options below may be used to determine the files
+shown:
+
+OPTIONS
+-------
+-c|--cached::
+ Show cached files in the output (default)
+
+-d|--deleted::
+ Show deleted files in the output
+
+-m|--modified::
+ Show modified files in the output
+
+-o|--others::
+ Show other files in the output
+
+-i|--ignored::
+ Show ignored files in the output
+ Note the this also reverses any exclude list present.
+
+-s|--stage::
+ Show stage files in the output
+
+-u|--unmerged::
+ Show unmerged files in the output (forces --stage)
+
+-k|--killed::
+ Show files on the filesystem that need to be removed due
+ to file/directory conflicts for checkout-index to
+ succeed.
+
+-z::
+ \0 line termination on output.
+
+-x|--exclude=<pattern>::
+ Skips files matching pattern.
+ Note that pattern is a shell wildcard pattern.
+
+-X|--exclude-from=<file>::
+ exclude patterns are read from <file>; 1 per line.
+
+--exclude-per-directory=<file>::
+ read additional exclude patterns that apply only to the
+ directory and its subdirectories in <file>.
+
+-t::
+ Identify the file status with the following tags (followed by
+ a space) at the start of each line:
+ H:: cached
+ M:: unmerged
+ R:: removed/deleted
+ C:: modifed/changed
+ K:: to be killed
+ ? other
+
+--::
+ Do not interpret any more arguments as options.
+
+<file>::
+ Files to show. If no files are given all files which match the other
+ specified criteria are shown.
+
+Output
+------
+show files just outputs the filename unless '--stage' is specified in
+which case it outputs:
+
+ [<tag> ]<mode> <object> <stage> <file>
+
+"git-ls-files --unmerged" and "git-ls-files --stage" can be used to examine
+detailed information on unmerged paths.
+
+For an unmerged path, instead of recording a single mode/SHA1 pair,
+the dircache records up to three such pairs; one from tree O in stage
+1, A in stage 2, and B in stage 3. This information can be used by
+the user (or the porcelain) to see what should eventually be recorded at the
+path. (see git-read-tree for more information on state)
+
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
+
+Exclude Patterns
+----------------
+
+'git-ls-files' can use a list of "exclude patterns" when
+traversing the directory tree and finding files to show when the
+flags --others or --ignored are specified.
+
+These exclude patterns come from these places:
+
+ 1. command line flag --exclude=<pattern> specifies a single
+ pattern.
+
+ 2. command line flag --exclude-from=<file> specifies a list of
+ patterns stored in a file.
+
+ 3. command line flag --exclude-per-directory=<name> specifies
+ a name of the file in each directory 'git-ls-files'
+ examines, and if exists, its contents are used as an
+ additional list of patterns.
+
+An exclude pattern file used by (2) and (3) contains one pattern
+per line. A line that starts with a '#' can be used as comment
+for readability.
+
+There are three lists of patterns that are in effect at a given
+time. They are built and ordered in the following way:
+
+ * --exclude=<pattern> from the command line; patterns are
+ ordered in the same order as they appear on the command line.
+
+ * lines read from --exclude-from=<file>; patterns are ordered
+ in the same order as they appear in the file.
+
+ * When --exclude-per-directory=<name> is specified, upon
+ entering a directory that has such a file, its contents are
+ appended at the end of the current "list of patterns". They
+ are popped off when leaving the directory.
+
+Each pattern in the pattern list specifies "a match pattern" and
+optionally the fate; either a file that matches the pattern is
+considered excluded or included. A filename is matched against
+the patterns in the three lists; the --exclude-from list is
+checked first, then the --exclude-per-directory list, and then
+finally the --exclude list. The last match determines its fate.
+If there is no match in the three lists, the fate is "included".
+
+A pattern specified on the command line with --exclude or read
+from the file specified with --exclude-from is relative to the
+top of the directory tree. A pattern read from a file specified
+by --exclude-per-directory is relative to the directory that the
+pattern file appears in.
+
+An exclude pattern is of the following format:
+
+ - an optional prefix '!' which means that the fate this pattern
+ specifies is "include", not the usual "exclude"; the
+ remainder of the pattern string is interpreted according to
+ the following rules.
+
+ - if it does not contain a slash '/', it is a shell glob
+ pattern and used to match against the filename without
+ leading directories (i.e. the same way as the current
+ implementation).
+
+ - otherwise, it is a shell glob pattern, suitable for
+ consumption by fnmatch(3) with FNM_PATHNAME flag. I.e. a
+ slash in the pattern must match a slash in the pathname.
+ "Documentation/\*.html" matches "Documentation/git.html" but
+ not "ppc/ppc.html". As a natural exception, "/*.c" matches
+ "cat-file.c" but not "mozilla-sha1/sha1.c".
+
+An example:
+
+--------------------------------------------------------------
+ $ cat .git/ignore
+ # ignore objects and archives, anywhere in the tree.
+ *.[oa]
+ $ cat Documentation/.gitignore
+ # ignore generated html files,
+ *.html
+ # except foo.html which is maintained by hand
+ !foo.html
+ $ git-ls-files --ignored \
+ --exclude='Documentation/*.[0-9]' \
+ --exclude-from=.git/ignore \
+ --exclude-per-directory=.gitignore
+--------------------------------------------------------------
+
+
+See Also
+--------
+gitlink:git-read-tree[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt
new file mode 100644
index 000000000..c0a80d408
--- /dev/null
+++ b/Documentation/git-ls-remote.txt
@@ -0,0 +1,63 @@
+git-ls-remote(1)
+================
+
+NAME
+----
+git-ls-remote - Look at references other repository has.
+
+
+SYNOPSIS
+--------
+'git-ls-remote' [--heads] [--tags] <repository> <refs>...
+
+DESCRIPTION
+-----------
+Displays the references other repository has.
+
+
+OPTIONS
+-------
+--heads --tags::
+ Limit to only refs/heads and refs/tags, respectively.
+ These options are _not_ mutually exclusive; when given
+ both, references stored in refs/heads and refs/tags are
+ displayed.
+
+<repository>::
+ Location of the repository. The shorthand defined in
+ $GIT_DIR/branches/ can be used.
+
+<refs>...::
+ When unspecified, all references, after filtering done
+ with --heads and --tags, are shown. When <refs>... are
+ specified, only references matching the given patterns
+ are displayed.
+
+EXAMPLES
+--------
+
+ $ git ls-remote --tags ./.
+ d6602ec5194c87b0fc87103ca4d67251c76f233a refs/tags/v0.99
+ f25a265a342aed6041ab0cc484224d9ca54b6f41 refs/tags/v0.99.1
+ 7ceca275d047c90c0c7d5afb13ab97efdf51bd6e refs/tags/v0.99.3
+ c5db5456ae3b0873fc659c19fafdde22313cc441 refs/tags/v0.99.2
+ 0918385dbd9656cab0d1d81ba7453d49bbc16250 refs/tags/junio-gpg-pub
+ $ 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\*
+ d6602ec5194c87b0fc87103ca4d67251c76f233a refs/tags/v0.99
+ f25a265a342aed6041ab0cc484224d9ca54b6f41 refs/tags/v0.99.1
+ c5db5456ae3b0873fc659c19fafdde22313cc441 refs/tags/v0.99.2
+ 7ceca275d047c90c0c7d5afb13ab97efdf51bd6e refs/tags/v0.99.3
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
new file mode 100644
index 000000000..ba0438e9a
--- /dev/null
+++ b/Documentation/git-ls-tree.txt
@@ -0,0 +1,58 @@
+git-ls-tree(1)
+==============
+
+NAME
+----
+git-ls-tree - Lists the contents of a tree object.
+
+
+SYNOPSIS
+--------
+'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...]
+
+DESCRIPTION
+-----------
+Lists the contents of a tree object, like what "/bin/ls -a" does
+in the current working directory.
+
+OPTIONS
+-------
+<tree-ish>::
+ Id of a tree-ish.
+
+-d::
+ show only the named tree entry itself, not its children
+
+-r::
+ recurse into sub-trees
+
+-z::
+ \0 line termination on output
+
+paths::
+ When paths are given, show them. Otherwise implicitly
+ uses the root level of the tree as the sole path argument.
+
+
+Output Format
+-------------
+ <mode> SP <type> SP <object> TAB <file>
+
+When `-z` option is not used, TAB, LF, and backslash characters
+in pathnames are represented as `\t`, `\n`, and `\\`,
+respectively.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+Completely rewritten from scratch by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
new file mode 100644
index 000000000..dc7d725ea
--- /dev/null
+++ b/Documentation/git-mailinfo.txt
@@ -0,0 +1,65 @@
+git-mailinfo(1)
+===============
+
+NAME
+----
+git-mailinfo - Extracts patch from a single e-mail message.
+
+
+SYNOPSIS
+--------
+'git-mailinfo' [-k] [-u] <msg> <patch>
+
+
+DESCRIPTION
+-----------
+Reading a single e-mail message from the standard input, and
+writes the commit log message in <msg> file, and the patches in
+<patch> file. The author name, e-mail and e-mail subject are
+written out to the standard output to be used by git-applypatch
+to create a commit. It is usually not necessary to use this
+command directly.
+
+
+OPTIONS
+-------
+-k::
+ Usually the program 'cleans up' the Subject: header line
+ to extract the title line for the commit log message,
+ among which (1) remove 'Re:' or 're:', (2) leading
+ whitespaces, (3) '[' up to ']', typically '[PATCH]', and
+ then prepends "[PATCH] ". This flag forbids this
+ munging, and is most useful when used to read back 'git
+ format-patch --mbox' output.
+
+-u::
+ By default, the commit log message, author name and
+ author email are taken from the e-mail without any
+ charset conversion, after minimally decoding MIME
+ transfer encoding. This flag causes the resulting
+ commit to be encoded in utf-8 by transliterating them.
+ Note that the patch is always used as is without charset
+ conversion, even with this flag.
+
+<msg>::
+ The commit log message extracted from e-mail, usually
+ except the title line which comes from e-mail Subject.
+
+<patch>::
+ The patch extracted from e-mail.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt
new file mode 100644
index 000000000..03a947766
--- /dev/null
+++ b/Documentation/git-mailsplit.txt
@@ -0,0 +1,45 @@
+git-mailsplit(1)
+================
+
+NAME
+----
+git-mailsplit - Totally braindamaged mbox splitter program.
+
+SYNOPSIS
+--------
+'git-mailsplit' [-d<prec>] [<mbox>] <directory>
+
+DESCRIPTION
+-----------
+Splits a mbox file into a list of files: "0001" "0002" .. in the specified
+directory so you can process them further from there.
+
+OPTIONS
+-------
+<mbox>::
+ Mbox file to split. If not given, the mbox is read from
+ the standard input.
+
+<directory>::
+ Directory in which to place the individual messages.
+
+-d<prec>::
+ Instead of the default 4 digits with leading zeros,
+ different precision can be specified for the generated
+ filenames.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+and Junio C Hamano <junkio@cox.net>
+
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt
new file mode 100644
index 000000000..d1d56f194
--- /dev/null
+++ b/Documentation/git-merge-base.txt
@@ -0,0 +1,33 @@
+git-merge-base(1)
+=================
+
+NAME
+----
+git-merge-base - Finds as good a common ancestor as possible for a merge
+
+
+SYNOPSIS
+--------
+'git-merge-base' <commit> <commit>
+
+DESCRIPTION
+-----------
+"git-merge-base" finds as good a common ancestor as possible. Given a
+selection of equally good common ancestors it should not be relied on
+to decide in any particular way.
+
+The "git-merge-base" algorithm is still in flux - use the source...
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt
new file mode 100644
index 000000000..60306429f
--- /dev/null
+++ b/Documentation/git-merge-index.txt
@@ -0,0 +1,88 @@
+git-merge-index(1)
+==================
+
+NAME
+----
+git-merge-index - Runs a merge for files needing merging
+
+
+SYNOPSIS
+--------
+'git-merge-index' [-o] [-q] <merge-program> (-a | -- | <file>\*)
+
+DESCRIPTION
+-----------
+This looks up the <file>(s) in the index and, if there are any merge
+entries, passes the SHA1 hash for those files as arguments 1, 2, 3 (empty
+argument if no file), and <file> as argument 4. File modes for the three
+files are passed as arguments 5, 6 and 7.
+
+OPTIONS
+-------
+--::
+ Interpret all following arguments as filenames.
+
+-a::
+ Run merge against all files in the index that need merging.
+
+-o::
+ Instead of stopping at the first failed merge, do all of them
+ in one shot - continue with merging even when previous merges
+ returned errors, and only return the error code after all the
+ merges are over.
+
+-q::
+ Do not complain about failed merge program (the merge program
+ failure usually indicates conflicts during merge). This is for
+ porcelains which might want to emit custom messages.
+
+If "git-merge-index" is called with multiple <file>s (or -a) then it
+processes them in turn only stopping if merge returns a non-zero exit
+code.
+
+Typically this is run with the a script calling the merge command from
+the RCS package.
+
+A sample script called "git-merge-one-file" is included in the
+distribution.
+
+ALERT ALERT ALERT! The git "merge object order" is different from the
+RCS "merge" program merge object order. In the above ordering, the
+original is first. But the argument order to the 3-way merge program
+"merge" is to have the original in the middle. Don't ask me why.
+
+Examples:
+
+ torvalds@ppc970:~/merge-test> git-merge-index cat MM
+ This is MM from the original tree. # original
+ This is modified MM in the branch A. # merge1
+ This is modified MM in the branch B. # merge2
+ This is modified MM in the branch B. # current contents
+
+or
+
+ torvalds@ppc970:~/merge-test> git-merge-index cat AA MM
+ cat: : No such file or directory
+ This is added AA in the branch A.
+ This is added AA in the branch B.
+ This is added AA in the branch B.
+ fatal: merge program failed
+
+where the latter example shows how "git-merge-index" will stop trying to
+merge once anything has returned an error (ie "cat" returned an error
+for the AA file, because it didn't exist in the original, and thus
+"git-merge-index" didn't even try to merge the MM thing).
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+One-shot merge by Petr Baudis <pasky@ucw.cz>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-merge-one-file.txt b/Documentation/git-merge-one-file.txt
new file mode 100644
index 000000000..86aad37c6
--- /dev/null
+++ b/Documentation/git-merge-one-file.txt
@@ -0,0 +1,30 @@
+git-merge-one-file(1)
+=====================
+
+NAME
+----
+git-merge-one-file - The standard helper program to use with "git-merge-index"
+
+
+SYNOPSIS
+--------
+'git-merge-one-file'
+
+DESCRIPTION
+-----------
+This is the standard helper program to use with "git-merge-index"
+to resolve a merge after the trivial merge done with "git-read-tree -m".
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>,
+Junio C Hamano <junkio@cox.net> and Petr Baudis <pasky@suse.cz>.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt
new file mode 100644
index 000000000..904e2fc4c
--- /dev/null
+++ b/Documentation/git-merge.txt
@@ -0,0 +1,56 @@
+git-merge(1)
+============
+
+NAME
+----
+git-merge - Grand Unified Merge Driver
+
+
+SYNOPSIS
+--------
+'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>...
+
+
+DESCRIPTION
+-----------
+This is the top-level user interface to the merge machinery
+which drives multiple merge strategy scripts.
+
+
+OPTIONS
+-------
+include::merge-options.txt[]
+
+<msg>::
+ The commit message to be used for the merge commit (in case
+ it is created). The `git-fmt-merge-msg` script can be used
+ to give a good default for automated `git-merge` invocations.
+
+<head>::
+ our branch head commit.
+
+<remote>::
+ other branch head merged into our branch. You need at
+ least one <remote>. Specifying more than one <remote>
+ obviously means you are trying an Octopus.
+
+include::merge-strategies.txt[]
+
+
+SEE ALSO
+--------
+gitlink:git-fmt-merge-msg[1], gitlink:git-pull[1]
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-mktag.txt b/Documentation/git-mktag.txt
new file mode 100644
index 000000000..2860a3d1b
--- /dev/null
+++ b/Documentation/git-mktag.txt
@@ -0,0 +1,47 @@
+git-mktag(1)
+============
+
+NAME
+----
+git-mktag - Creates a tag object
+
+
+SYNOPSIS
+--------
+'git-mktag' < signature_file
+
+DESCRIPTION
+-----------
+Reads a tag contents on standard input and creates a tag object
+that can also be used to sign other objects.
+
+The output is the new tag's <object> identifier.
+
+Tag Format
+----------
+A tag signature file has a very simple fixed format: three lines of
+
+ object <sha1>
+ type <typename>
+ tag <tagname>
+
+followed by some 'optional' free-form signature that git itself
+doesn't care about, but that can be verified with gpg or similar.
+
+The size of the full object is artificially limited to 8kB. (Just
+because I'm a lazy bastard, and if you can't fit a signature in that
+size, you're doing something wrong)
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt
new file mode 100644
index 000000000..f2d5882f4
--- /dev/null
+++ b/Documentation/git-mv.txt
@@ -0,0 +1,51 @@
+git-mv(1)
+=========
+
+NAME
+----
+git-mv - Script used to move or rename a file, directory or symlink.
+
+
+SYNOPSIS
+--------
+'git-mv' [-f] [-n] <source> <destination>
+'git-mv' [-f] [-k] [-n] <source> ... <destination directory>
+
+DESCRIPTION
+-----------
+This script is used to move or rename a file, directory or symlink.
+In the first form, it renames <source>, which must exist and be either
+a file, symlink or directory, to <destination>, which must not exist.
+In the second form, the last argument has to be an existing
+directory; the given sources will be moved into this directory.
+
+The index is updated after successful completion, but the change must still be
+committed.
+
+OPTIONS
+-------
+-f::
+ Force renaming or moving even targets exist
+-k::
+ Skip move or rename actions which would lead to an error
+ condition. An error happens when a source is neither existing nor
+ controlled by GIT, or when it would overwrite an existing
+ file unless '-f' is given.
+-n::
+ Do nothing; only show what would happen
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+Rewritten by Ryan Anderson <ryan@michonline.com>
+Move functionality added by Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt
new file mode 100644
index 000000000..e37b0b8f9
--- /dev/null
+++ b/Documentation/git-name-rev.txt
@@ -0,0 +1,66 @@
+git-name-rev(1)
+===============
+
+NAME
+----
+git-name-rev - Find symbolic names for given revs.
+
+
+SYNOPSIS
+--------
+'git-name-rev' [--tags] ( --all | --stdin | <commitish>... )
+
+DESCRIPTION
+-----------
+Finds symbolic names suitable for human digestion for revisions given in any
+format parsable by git-rev-parse.
+
+
+OPTIONS
+-------
+
+--tags::
+ Do not use branch names, but only tags to name the commits
+
+--all::
+ List all commits reachable from all refs
+
+--stdin::
+ Read from stdin, append "(<rev_name>)" to all sha1's of name'able
+ commits, and pass to stdout
+
+EXAMPLE
+-------
+
+Given a commit, find out where it is relative to the local refs. Say somebody
+wrote you about that phantastic commit 33db5f4d9027a10e477ccf054b2c1ab94f74c85a.
+Of course, you look into the commit, but that only tells you what happened, but
+not the context.
+
+Enter git-name-rev:
+
+------------
+% git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
+------------
+
+Now you are wiser, because you know that it happened 940 revisions before v0.99.
+
+Another nice thing you can do is:
+
+------------
+% git log | git name-rev --stdin
+------------
+
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
+
+Documentation
+--------------
+Documentation by Johannes Schindelin.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-octopus.txt b/Documentation/git-octopus.txt
new file mode 100644
index 000000000..6e32ea347
--- /dev/null
+++ b/Documentation/git-octopus.txt
@@ -0,0 +1,38 @@
+git-octopus(1)
+==============
+
+NAME
+----
+git-octopus - Merge more than two commits.
+
+
+SYNOPSIS
+--------
+'git-octopus'
+
+DESCRIPTION
+-----------
+After running 'git fetch', $GIT_DIR/FETCH_HEAD contains the
+following information, one line per remote ref:
+
+------------------------------------------------
+<object name> <ref name> from <repository>
+------------------------------------------------
+
+Using this information, create and commit an Octopus merge on
+top of the current HEAD.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
new file mode 100644
index 000000000..d1e93dbb3
--- /dev/null
+++ b/Documentation/git-pack-objects.txt
@@ -0,0 +1,89 @@
+git-pack-objects(1)
+===================
+
+NAME
+----
+git-pack-objects - Create a packed archive of objects.
+
+
+SYNOPSIS
+--------
+'git-pack-objects' [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list
+
+
+DESCRIPTION
+-----------
+Reads list of objects from the standard input, and writes a packed
+archive with specified base-name, or to the standard output.
+
+A packed archive is an efficient way to transfer set of objects
+between two repositories, and also is an archival format which
+is efficient to access. The packed archive format (.pack) is
+designed to be unpackable without having anything else, but for
+random access, accompanied with the pack index file (.idx).
+
+'git-unpack-objects' command can read the packed archive and
+expand the objects contained in the pack into "one-file
+one-object" format; this is typically done by the smart-pull
+commands when a pack is created on-the-fly for efficient network
+transport by their peers.
+
+Placing both in the pack/ subdirectory of $GIT_OBJECT_DIRECTORY (or
+any of the directories on $GIT_ALTERNATE_OBJECT_DIRECTORIES)
+enables git to read from such an archive.
+
+
+OPTIONS
+-------
+base-name::
+ Write into a pair of files (.pack and .idx), using
+ <base-name> to determine the name of the created file.
+ When this option is used, the two files are written in
+ <base-name>-<SHA1>.{pack,idx} files. <SHA1> is a hash
+ of object names (currently in random order so it does
+ not have any useful meaning) to make the resulting
+ filename reasonably unique, and written to the standard
+ output of the command.
+
+--stdout::
+ Write the pack contents (what would have been writtin to
+ .pack file) out to the standard output.
+
+--window and --depth::
+ These two options affects how the objects contained in
+ the pack are stored using delta compression. The
+ objects are first internally sorted by type, size and
+ optionally names and compared against the other objects
+ within --window to see if using delta compression saves
+ space. --depth limits the maximum delta depth; making
+ it too deep affects the performance on the unpacker
+ side, because delta data needs to be applied that many
+ times to get to the necessary object.
+
+--incremental::
+ This flag causes an object already in a pack ignored
+ even if it appears in the standard input.
+
+--local::
+ This flag is similar to `--incremental`; instead of
+ ignoring all packed objects, it only ignores objects
+ that are packed and not in the local object store
+ (i.e. borrowed from an alternate).
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+-------------
+Documentation by Junio C Hamano
+
+See-Also
+--------
+gitlink:git-repack[1]
+gitlink:git-prune-packed[1]
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt
new file mode 100644
index 000000000..9fe86aef9
--- /dev/null
+++ b/Documentation/git-pack-redundant.txt
@@ -0,0 +1,58 @@
+git-pack-redundant(1)
+=====================
+
+NAME
+----
+git-pack-redundant - Program used to find redundant pack files.
+
+
+SYNOPSIS
+--------
+'git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | .pack filename ... >'
+
+DESCRIPTION
+-----------
+This program computes which packs in your repository
+are redundant. The output is suitable for piping to
+'xargs rm' if you are in the root of the repository.
+
+git-pack-redundant accepts a list of objects on standard input. Any objects
+given will be ignored when checking which packs are required. This makes the
+following command useful when wanting to remove packs which contain unreachable
+objects.
+
+git-fsck-objects --full --unreachable | cut -d ' ' -f3 | \
+git-pack-redundant --all | xargs rm
+
+OPTIONS
+-------
+
+
+--all::
+ Processes all packs. Any filenames on the commandline are ignored.
+
+--alt-odb::
+ Don't require objects present in packs from alternate object
+ directories to be present in local packs.
+
+--verbose::
+ Outputs some statistics to stderr. Has a small performance penalty.
+
+Author
+------
+Written by Lukas Sandström <lukass@etek.chalmers.se>
+
+Documentation
+--------------
+Documentation by Lukas Sandström <lukass@etek.chalmers.se>
+
+See-Also
+--------
+gitlink:git-pack-objects[1]
+gitlink:git-repack[1]
+gitlink:git-prune-packed[1]
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-parse-remote.txt b/Documentation/git-parse-remote.txt
new file mode 100644
index 000000000..fc27afe26
--- /dev/null
+++ b/Documentation/git-parse-remote.txt
@@ -0,0 +1,48 @@
+git-parse-remote(1)
+===================
+
+NAME
+----
+git-parse-remote - Routines to help parsing $GIT_DIR/remotes/
+
+
+SYNOPSIS
+--------
+'. git-parse-remote'
+
+DESCRIPTION
+-----------
+This script is included in various scripts to supply
+routines to parse files under $GIT_DIR/remotes/ and
+$GIT_DIR/branches/.
+
+The primary entry points are:
+
+get_remote_refs_for_fetch::
+ Given the list of user-supplied `<repo> <refspec>...`,
+ return the list of refs to fetch after canonicalizing
+ them into `$GIT_DIR` relative paths
+ (e.g. `refs/heads/foo`). When `<refspec>...` is empty
+ the returned list of refs consists of the defaults
+ for the given `<repo>`, if specified in
+ `$GIT_DIR/remotes/` or `$GIT_DIR/branches/`.
+
+get_remote_refs_for_push::
+ Given the list of user-supplied `<repo> <refspec>...`,
+ return the list of refs to push in a form suitable to be
+ fed to the `git-send-pack` command. When `<refspec>...`
+ is empty the returned list of refs consists of the
+ defaults for the given `<repo>`, if specified in
+ `$GIT_DIR/remotes/`.
+
+Author
+------
+Written by Junio C Hamano.
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt
new file mode 100644
index 000000000..c8bd19777
--- /dev/null
+++ b/Documentation/git-patch-id.txt
@@ -0,0 +1,43 @@
+git-patch-id(1)
+===============
+
+NAME
+----
+git-patch-id - Generate a patch ID.
+
+SYNOPSIS
+--------
+'git-patch-id' < <patch>
+
+DESCRIPTION
+-----------
+A "patch ID" is nothing but a SHA1 of the diff associated with a patch, with
+whitespace and line numbers ignored. As such, it's "reasonably stable", but at
+the same time also reasonably unique, ie two patches that have the same "patch
+ID" are almost guaranteed to be the same thing.
+
+IOW, you can use this thing to look for likely duplicate commits.
+
+When dealing with git-diff-tree output, it takes advantage of
+the fact that the patch is prefixed with the object name of the
+commit, and outputs two 40-byte hexadecimal string. The first
+string is the patch ID, and the second string is the commit ID.
+This can be used to make a mapping from patch ID to commit ID.
+
+OPTIONS
+-------
+<patch>::
+ The diff to create the ID of.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt
new file mode 100644
index 000000000..915d3f8a0
--- /dev/null
+++ b/Documentation/git-peek-remote.txt
@@ -0,0 +1,52 @@
+git-peek-remote(1)
+==================
+
+NAME
+----
+git-peek-remote - Lists the references in a remote repository.
+
+
+SYNOPSIS
+--------
+'git-peek-remote' [--exec=<git-upload-pack>] [<host>:]<directory>
+
+DESCRIPTION
+-----------
+Lists the references the remote repository has, and optionally
+stores them in the local repository under the same name.
+
+OPTIONS
+-------
+--exec=<git-upload-pack>::
+ Use this to specify the path to 'git-upload-pack' on the
+ remote side, if it is not found on your $PATH. Some
+ installations of sshd ignores the user's environment
+ setup scripts for login shells (e.g. .bash_profile) and
+ your privately installed git may not be found on the system
+ default $PATH. Another workaround suggested is to set
+ up your $PATH in ".bashrc", but this flag is for people
+ who do not want to pay the overhead for non-interactive
+ shells, but prefer having a lean .bashrc file (they set most of
+ the things up in .bash_profile).
+
+<host>::
+ A remote host that houses the repository. When this
+ part is specified, 'git-upload-pack' is invoked via
+ ssh.
+
+<directory>::
+ The repository to sync from.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt
new file mode 100644
index 000000000..8d96a91b4
--- /dev/null
+++ b/Documentation/git-prune-packed.txt
@@ -0,0 +1,48 @@
+git-prune-packed(1)
+=====================
+
+NAME
+----
+git-prune-packed - Program used to remove the extra object files that are now
+residing in a pack file.
+
+
+SYNOPSIS
+--------
+'git-prune-packed'
+
+DESCRIPTION
+-----------
+This program search the GIT_OBJECT_DIR for all objects that currently exist in
+a pack file as well as the independent object directories.
+
+All such extra objects are removed.
+
+A pack is a collection of objects, individually compressed, with delta
+compression applied, stored in a single file, with an associated index file.
+
+Packs are used to reduce the load on mirror systems, backup engines, disk storage, etc.
+
+OPTIONS
+-------
+-n::
+ Don't actually remove any objects, only show those that would have been
+ removed.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Ryan Anderson <ryan@michonline.com>
+
+See-Also
+--------
+gitlink:git-pack-objects[1]
+gitlink:git-repack[1]
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
new file mode 100644
index 000000000..3367c9b21
--- /dev/null
+++ b/Documentation/git-prune.txt
@@ -0,0 +1,42 @@
+git-prune(1)
+============
+
+NAME
+----
+git-prune - Prunes all unreachable objects from the object database
+
+
+SYNOPSIS
+--------
+'git-prune' [-n]
+
+DESCRIPTION
+-----------
+
+This runs `git-fsck-objects --unreachable` using the heads
+specified on the command line (or `$GIT_DIR/refs/heads/\*` and
+`$GIT_DIR/refs/tags/\*` if none is specified), and prunes all
+unreachable objects from the object database. In addition, it
+prunes the unpacked objects that are also found in packs by
+running `git prune-packed`.
+
+OPTIONS
+-------
+
+-n::
+ Do not remove anything; just report what it would
+ remove.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt
new file mode 100644
index 000000000..c65ca9a53
--- /dev/null
+++ b/Documentation/git-pull.txt
@@ -0,0 +1,126 @@
+git-pull(1)
+===========
+
+NAME
+----
+git-pull - Pull and merge from another repository.
+
+
+SYNOPSIS
+--------
+'git-pull' <options> <repository> <refspec>...
+
+
+DESCRIPTION
+-----------
+Runs `git-fetch` with the given parameters, and calls `git-merge`
+to merge the retrieved head(s) into the current branch.
+
+Note that you can use `.` (current directory) as the
+<repository> to pull from the local repository -- this is useful
+when merging local branches into the current branch.
+
+
+OPTIONS
+-------
+include::merge-options.txt[]
+
+include::fetch-options.txt[]
+
+include::pull-fetch-param.txt[]
+
+include::merge-strategies.txt[]
+
+
+EXAMPLES
+--------
+
+git pull, git pull origin::
+ Fetch the default head from the repository you cloned
+ from and merge it into your current branch.
+
+git pull -s ours . obsolete::
+ Merge local branch `obsolete` into the current branch,
+ using `ours` merge strategy.
+
+git pull . fixes enhancements::
+ Bundle local branch `fixes` and `enhancements` on top of
+ the current branch, making an Octopus merge.
+
+git pull --no-commit . maint::
+ Merge local branch `maint` into the current branch, but
+ do not make a commit automatically. This can be used
+ when you want to include further changes to the merge,
+ or want to write your own merge commit message.
++
+You should refrain from abusing this option to sneak substantial
+changes into a merge commit. Small fixups like bumping
+release/version name would be acceptable.
+
+Command line pull of multiple branches from one repository::
++
+------------------------------------------------
+$ cat .git/remotes/origin
+URL: git://git.kernel.org/pub/scm/git/git.git
+Pull: master:origin
+
+$ git checkout master
+$ git fetch origin master:origin +pu:pu maint:maint
+$ git pull . origin
+------------------------------------------------
++
+Here, a typical `.git/remotes/origin` file from a
+`git-clone` operation is used in combination with
+command line options to `git-fetch` to first update
+multiple branches of the local repository and then
+to merge the remote `origin` branch into the local
+`master` branch. The local `pu` branch is updated
+even if it does not result in a fast forward update.
+Here, the pull can obtain its objects from the local
+repository using `.`, as the previous `git-fetch` is
+known to have already obtained and made available
+all the necessary objects.
+
+
+Pull of multiple branches from one repository using `.git/remotes` file::
++
+------------------------------------------------
+$ cat .git/remotes/origin
+URL: git://git.kernel.org/pub/scm/git/git.git
+Pull: master:origin
+Pull: +pu:pu
+Pull: maint:maint
+
+$ git checkout master
+$ git pull origin
+------------------------------------------------
++
+Here, a typical `.git/remotes/origin` file from a
+`git-clone` operation has been hand-modified to include
+the branch-mapping of additional remote and local
+heads directly. A single `git-pull` operation while
+in the `master` branch will fetch multiple heads and
+merge the remote `origin` head into the current,
+local `master` branch.
+
+
+SEE ALSO
+--------
+gitlink:git-fetch[1], gitlink:git-merge[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+and Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Jon Loeliger,
+David Greaves,
+Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
new file mode 100644
index 000000000..f45ac5ee4
--- /dev/null
+++ b/Documentation/git-push.txt
@@ -0,0 +1,45 @@
+git-push(1)
+===========
+
+NAME
+----
+git-push - Update remote refs along with associated objects.
+
+
+SYNOPSIS
+--------
+'git-push' [--all] [--force] <repository> <refspec>...
+
+DESCRIPTION
+-----------
+
+Updates remote refs using local refs, while sending objects
+necessary to complete the given refs.
+
+
+OPTIONS
+-------
+include::pull-fetch-param.txt[]
+
+\--all::
+ Instead of naming each ref to push, specifies all refs
+ to be pushed.
+
+-f, \--force::
+ Usually, the command refuses to update a local ref that is
+ not an ancestor of the remote ref used to overwrite it.
+ This flag disables the check. What this means is that the
+ local repository can lose commits; use it with care.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
new file mode 100644
index 000000000..8b9184785
--- /dev/null
+++ b/Documentation/git-read-tree.txt
@@ -0,0 +1,281 @@
+git-read-tree(1)
+================
+
+NAME
+----
+git-read-tree - Reads tree information into the index
+
+
+SYNOPSIS
+--------
+'git-read-tree' (<tree-ish> | [-m [-u|-i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+
+
+DESCRIPTION
+-----------
+Reads the tree information given by <tree-ish> into the index,
+but does not actually *update* any of the files it "caches". (see:
+git-checkout-index)
+
+Optionally, it can merge a tree into the index, perform a
+fast-forward (i.e. 2-way) merge, or a 3-way merge, with the -m
+flag. When used with -m, the -u flag causes it to also update
+the files in the work tree with the result of the merge.
+
+Trivial merges are done by "git-read-tree" itself. Only conflicting paths
+will be in unmerged state when "git-read-tree" returns.
+
+OPTIONS
+-------
+-m::
+ Perform a merge, not just a read.
+
+--reset::
+
+ Same as -m except that unmerged entries will be silently ignored.
+
+-u::
+ After a successful merge, update the files in the work
+ tree with the result of the merge.
+
+-i::
+ Usually a merge requires the index file as well as the
+ files in the working tree are up to date with the
+ current head commit, in order not to lose local
+ changes. This flag disables the check with the working
+ tree and is meant to be used when creating a merge of
+ trees that are not directly related to the current
+ working tree status into a temporary index file.
+
+
+<tree-ish#>::
+ The id of the tree object(s) to be read/merged.
+
+
+Merging
+-------
+If '-m' is specified, "git-read-tree" can perform 3 kinds of
+merge, a single tree merge if only 1 tree is given, a
+fast-forward merge with 2 trees, or a 3-way merge if 3 trees are
+provided.
+
+
+Single Tree Merge
+~~~~~~~~~~~~~~~~~
+If only 1 tree is specified, git-read-tree operates as if the user did not
+specify '-m', except that if the original index has an entry for a
+given pathname, and the contents of the path matches with the tree
+being read, the stat info from the index is used. (In other words, the
+index's stat()s take precedence over the merged tree's).
+
+That means that if you do a "git-read-tree -m <newtree>" followed by a
+"git-checkout-index -f -u -a", the "git-checkout-index" only checks out
+the stuff that really changed.
+
+This is used to avoid unnecessary false hits when "git-diff-files" is
+run after git-read-tree.
+
+
+Two Tree Merge
+~~~~~~~~~~~~~~
+
+Typically, this is invoked as "git-read-tree -m $H $M", where $H
+is the head commit of the current repository, and $M is the head
+of a foreign tree, which is simply ahead of $H (i.e. we are in a
+fast forward situation).
+
+When two trees are specified, the user is telling git-read-tree
+the following:
+
+ 1. The current index and work tree is derived from $H, but
+ the user may have local changes in them since $H;
+
+ 2. The user wants to fast-forward to $M.
+
+In this case, the "git-read-tree -m $H $M" command makes sure
+that no local change is lost as the result of this "merge".
+Here are the "carry forward" rules:
+
+ I (index) H M Result
+ -------------------------------------------------------
+ 0 nothing nothing nothing (does not happen)
+ 1 nothing nothing exists use M
+ 2 nothing exists nothing remove path from index
+ 3 nothing exists exists use M
+
+ clean I==H I==M
+ ------------------
+ 4 yes N/A N/A nothing nothing keep index
+ 5 no N/A N/A nothing nothing keep index
+
+ 6 yes N/A yes nothing exists keep index
+ 7 no N/A yes nothing exists keep index
+ 8 yes N/A no nothing exists fail
+ 9 no N/A no nothing exists fail
+
+ 10 yes yes N/A exists nothing remove path from index
+ 11 no yes N/A exists nothing fail
+ 12 yes no N/A exists nothing fail
+ 13 no no N/A exists nothing fail
+
+ clean (H=M)
+ ------
+ 14 yes exists exists keep index
+ 15 no exists exists keep index
+
+ clean I==H I==M (H!=M)
+ ------------------
+ 16 yes no no exists exists fail
+ 17 no no no exists exists fail
+ 18 yes no yes exists exists keep index
+ 19 no no yes exists exists keep index
+ 20 yes yes no exists exists use M
+ 21 no yes no exists exists fail
+
+In all "keep index" cases, the index entry stays as in the
+original index file. If the entry were not up to date,
+git-read-tree keeps the copy in the work tree intact when
+operating under the -u flag.
+
+When this form of git-read-tree returns successfully, you can
+see what "local changes" you made are carried forward by running
+"git-diff-index --cached $M". Note that this does not
+necessarily match "git-diff-index --cached $H" would have
+produced before such a two tree merge. This is because of cases
+18 and 19 --- if you already had the changes in $M (e.g. maybe
+you picked it up via e-mail in a patch form), "git-diff-index
+--cached $H" would have told you about the change before this
+merge, but it would not show in "git-diff-index --cached $M"
+output after two-tree merge.
+
+
+3-Way Merge
+~~~~~~~~~~~
+Each "index" entry has two bits worth of "stage" state. stage 0 is the
+normal one, and is the only one you'd see in any kind of normal use.
+
+However, when you do "git-read-tree" with three trees, the "stage"
+starts out at 1.
+
+This means that you can do
+
+ git-read-tree -m <tree1> <tree2> <tree3>
+
+and you will end up with an index with all of the <tree1> entries in
+"stage1", all of the <tree2> entries in "stage2" and all of the
+<tree3> entries in "stage3".
+
+Furthermore, "git-read-tree" has special-case logic that says: if you see
+a file that matches in all respects in the following states, it
+"collapses" back to "stage0":
+
+ - stage 2 and 3 are the same; take one or the other (it makes no
+ difference - the same work has been done on stage 2 and 3)
+
+ - stage 1 and stage 2 are the same and stage 3 is different; take
+ stage 3 (some work has been done on stage 3)
+
+ - stage 1 and stage 3 are the same and stage 2 is different take
+ stage 2 (some work has been done on stage 2)
+
+The "git-write-tree" command refuses to write a nonsensical tree, and it
+will complain about unmerged entries if it sees a single entry that is not
+stage 0.
+
+Ok, this all sounds like a collection of totally nonsensical rules,
+but it's actually exactly what you want in order to do a fast
+merge. The different stages represent the "result tree" (stage 0, aka
+"merged"), the original tree (stage 1, aka "orig"), and the two trees
+you are trying to merge (stage 2 and 3 respectively).
+
+The order of stages 1, 2 and 3 (hence the order of three
+<tree-ish> command line arguments) are significant when you
+start a 3-way merge with an index file that is already
+populated. Here is an outline of how the algorithm works:
+
+- if a file exists in identical format in all three trees, it will
+ automatically collapse to "merged" state by git-read-tree.
+
+- a file that has _any_ difference what-so-ever in the three trees
+ will stay as separate entries in the index. It's up to "porcelain
+ policy" to determine how to remove the non-0 stages, and insert a
+ merged version.
+
+- the index file saves and restores with all this information, so you
+ can merge things incrementally, but as long as it has entries in
+ stages 1/2/3 (ie "unmerged entries") you can't write the result. So
+ now the merge algorithm ends up being really simple:
+
+ * you walk the index in order, and ignore all entries of stage 0,
+ since they've already been done.
+
+ * if you find a "stage1", but no matching "stage2" or "stage3", you
+ know it's been removed from both trees (it only existed in the
+ original tree), and you remove that entry.
+
+ * if you find a matching "stage2" and "stage3" tree, you remove one
+ of them, and turn the other into a "stage0" entry. Remove any
+ matching "stage1" entry if it exists too. .. all the normal
+ trivial rules ..
+
+You would normally use "git-merge-index" with supplied
+"git-merge-one-file" to do this last step. The script
+does not touch the files in the work tree, and the entire merge
+happens in the index file. In other words, there is no need to
+worry about what is in the working directory, since it is never
+shown and never used.
+
+When you start a 3-way merge with an index file that is already
+populated, it is assumed that it represents the state of the
+files in your work tree, and you can even have files with
+changes unrecorded in the index file. It is further assumed
+that this state is "derived" from the stage 2 tree. The 3-way
+merge refuses to run if it finds an entry in the original index
+file that does not match stage 2.
+
+This is done to prevent you from losing your work-in-progress
+changes. To illustrate, suppose you start from what has been
+commited last to your repository:
+
+ $ JC=`git-rev-parse --verify "HEAD^0"`
+ $ git-checkout-index -f -u -a $JC
+
+You do random edits, without running git-update-index. And then
+you notice that the tip of your "upstream" tree has advanced
+since you pulled from him:
+
+ $ git-fetch rsync://.... linus
+ $ LT=`cat .git/MERGE_HEAD`
+
+Your work tree is still based on your HEAD ($JC), but you have
+some edits since. Three-way merge makes sure that you have not
+added or modified index entries since $JC, and if you haven't,
+then does the right thing. So with the following sequence:
+
+ $ git-read-tree -m -u `git-merge-base $JC $LT` $JC $LT
+ $ git-merge-index git-merge-one-file -a
+ $ echo "Merge with Linus" | \
+ git-commit-tree `git-write-tree` -p $JC -p $LT
+
+what you would commit is a pure merge between $JC and LT without
+your work-in-progress changes, and your work tree would be
+updated to the result of the merge.
+
+
+See Also
+--------
+gitlink:git-write-tree[1]; gitlink:git-ls-files[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
new file mode 100644
index 000000000..16c158f43
--- /dev/null
+++ b/Documentation/git-rebase.txt
@@ -0,0 +1,35 @@
+git-rebase(1)
+=============
+
+NAME
+----
+git-rebase - Rebase local commits to new upstream head.
+
+SYNOPSIS
+--------
+'git-rebase' <upstream> [<head>]
+
+DESCRIPTION
+-----------
+Rebases local commits to the new head of the upstream tree.
+
+OPTIONS
+-------
+<upstream>::
+ Upstream branch to compare against.
+
+<head>::
+ Working branch; defaults to HEAD.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt
new file mode 100644
index 000000000..8afde1437
--- /dev/null
+++ b/Documentation/git-receive-pack.txt
@@ -0,0 +1,95 @@
+git-receive-pack(1)
+===================
+
+NAME
+----
+git-receive-pack - Receive what is pushed into it
+
+
+SYNOPSIS
+--------
+'git-receive-pack' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-send-pack' and updates the repository with the
+information fed from the remote end.
+
+This command is usually not invoked directly by the end user.
+The UI for the protocol is on the 'git-send-pack' side, and the
+program pair is meant to be used to push updates to remote
+repository. For pull operations, see 'git-fetch-pack' and
+'git-clone-pack'.
+
+The command allows for creation and fast forwarding of sha1 refs
+(heads/tags) on the remote end (strictly speaking, it is the
+local end receive-pack runs, but to the user who is sitting at
+the send-pack end, it is updating the remote. Confused?)
+
+Before each ref is updated, if $GIT_DIR/hooks/update file exists
+and executable, it is called with three parameters:
+
+ $GIT_DIR/hooks/update refname sha1-old sha1-new
+
+The refname parameter is relative to $GIT_DIR; e.g. for the
+master head this is "refs/heads/master". Two sha1 are the
+object names for the refname before and after the update. Note
+that the hook is called before the refname is updated, so either
+sha1-old is 0{40} (meaning there is no such ref yet), or it
+should match what is recorded in refname.
+
+The hook should exit with non-zero status if it wants to
+disallow updating the named ref. Otherwise it should exit with
+zero.
+
+Using this hook, it is easy to generate mails on updates to
+the local repository. This example script sends a mail with
+the commits pushed to the repository:
+
+ #!/bin/sh
+ # mail out commit update information.
+ if expr "$2" : '0*$' >/dev/null
+ then
+ echo "Created a new ref, with the following commits:"
+ git-rev-list --pretty "$2"
+ else
+ echo "New commits:"
+ git-rev-list --pretty "$3" "^$2"
+ fi |
+ mail -s "Changes to ref $1" commit-list@mydomain
+ exit 0
+
+Another hook $GIT_DIR/hooks/post-update, if exists and
+executable, is called with the list of refs that have been
+updated. This can be used to implement repository wide cleanup
+task if needed. The exit code from this hook invocation is
+ignored; the only thing left for git-receive-pack to do at that
+point is to exit itself anyway. This hook can be used, for
+example, to run "git-update-server-info" if the repository is
+packed and is served via a dumb transport.
+
+ #!/bin/sh
+ exec git-update-server-info
+
+OPTIONS
+-------
+<directory>::
+ The repository to sync into.
+
+
+SEE ALSO
+--------
+gitlink:git-send-pack[1]
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt
new file mode 100644
index 000000000..62405358f
--- /dev/null
+++ b/Documentation/git-relink.txt
@@ -0,0 +1,37 @@
+git-relink(1)
+=============
+
+NAME
+----
+git-relink - Hardlink common objects in local repositories.
+
+SYNOPSIS
+--------
+'git-relink' [--safe] <dir> <dir> [<dir>]\*
+
+DESCRIPTION
+-----------
+This will scan 2 or more object repositories and look for common objects, check
+if they are hardlinked, and replace one with a hardlink to the other if not.
+
+OPTIONS
+-------
+--safe::
+ Stops if two objects with the same hash exist but have different sizes.
+ Default is to warn and continue.
+
+<dir>::
+ Directories containing a .git/objects/ subdirectory.
+
+Author
+------
+Written by Ryan Anderson <ryan@michonline.com>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt
new file mode 100644
index 000000000..0c1ae49ed
--- /dev/null
+++ b/Documentation/git-repack.txt
@@ -0,0 +1,59 @@
+git-repack(1)
+=============
+
+NAME
+----
+git-repack - Script used to pack a repository from a collection of
+objects into pack files.
+
+
+SYNOPSIS
+--------
+'git-repack' [-a] [-d]
+
+DESCRIPTION
+-----------
+
+This script is used to combine all objects that do not currently
+reside in a "pack", into a pack.
+
+A pack is a collection of objects, individually compressed, with
+delta compression applied, stored in a single file, with an
+associated index file.
+
+Packs are used to reduce the load on mirror systems, backup
+engines, disk storage, etc.
+
+OPTIONS
+-------
+
+-a::
+ Instead of incrementally packing the unpacked objects,
+ pack everything available into a single pack.
+ Especially useful when packing a repository that is used
+ for a private development and there no need to worry
+ about people fetching via dumb protocols from it. Use
+ with '-d'.
+
+-d::
+ After packing, if the newly created packs make some
+ existing packs redundant, remove the redundant packs.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Ryan Anderson <ryan@michonline.com>
+
+See-Also
+--------
+gitlink:git-pack-objects[1]
+gitlink:git-prune-packed[1]
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt
new file mode 100644
index 000000000..2463ec91d
--- /dev/null
+++ b/Documentation/git-request-pull.txt
@@ -0,0 +1,40 @@
+git-request-pull(1)
+===================
+
+NAME
+----
+git-request-pull - Generates a summary of pending changes.
+
+SYNOPSIS
+--------
+'git-request-pull' <start> <url> [<end>]
+
+DESCRIPTION
+-----------
+
+Summarizes the changes between two commits to the standard output, and includes
+the given URL in the generated summary.
+
+OPTIONS
+-------
+<start>::
+ Commit to start at.
+
+<url>::
+ URL to include in the summary.
+
+<end>::
+ Commit to send at; defaults to HEAD.
+
+Author
+------
+Written by Ryan Anderson <ryan@michonline.com> and Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt
new file mode 100644
index 000000000..31ec2076e
--- /dev/null
+++ b/Documentation/git-reset.txt
@@ -0,0 +1,45 @@
+git-reset(1)
+============
+
+NAME
+----
+git-reset - Reset current HEAD to the specified state.
+
+SYNOPSIS
+--------
+'git-reset' [--mixed | --soft | --hard] [<commit-ish>]
+
+DESCRIPTION
+-----------
+Sets the current head to the specified commit and optionally resets the
+index and working tree to match.
+
+OPTIONS
+-------
+--mixed::
+ Like --soft but reports what has not been updated. This is the
+ default action.
+
+--soft::
+ Does not touch the index file nor the working tree at all, but
+ requires them in a good order.
+
+--hard::
+ Matches the working tree and index to that of the tree being
+ switched to.
+
+<commit-ish>::
+ Commit to make the current HEAD.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net> and Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-resolve.txt b/Documentation/git-resolve.txt
new file mode 100644
index 000000000..4e57c2b28
--- /dev/null
+++ b/Documentation/git-resolve.txt
@@ -0,0 +1,36 @@
+git-resolve(1)
+==============
+
+NAME
+----
+git-resolve - Merge two commits
+
+
+SYNOPSIS
+--------
+'git-resolve' <current> <merged> <message>
+
+DESCRIPTION
+-----------
+Given two commits and a merge message, merge the <merged> commit
+into <current> commit, with the commit log message <message>.
+
+When <current> is a descendant of <merged>, or <current> is an
+ancestor of <merged>, no new commit is created and the <message>
+is ignored. The former is informally called "already up to
+date", and the latter is often called "fast forward".
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Dan Holmsand <holmsand@gmail.com>.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
new file mode 100644
index 000000000..064ccb1f8
--- /dev/null
+++ b/Documentation/git-rev-list.txt
@@ -0,0 +1,150 @@
+git-rev-list(1)
+===============
+
+NAME
+----
+git-rev-list - Lists commit objects in reverse chronological order
+
+
+SYNOPSIS
+--------
+'git-rev-list' [ \--max-count=number ]
+ [ \--max-age=timestamp ]
+ [ \--min-age=timestamp ]
+ [ \--sparse ]
+ [ \--no-merges ]
+ [ \--all ]
+ [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] | ]
+ [ \--parents ]
+ [ \--objects [ \--unpacked ] ]
+ [ \--pretty | \--header | ]
+ [ \--bisect ]
+ <commit>... [ \-- <paths>... ]
+
+DESCRIPTION
+-----------
+Lists commit objects in reverse chronological order starting at the
+given commit(s), taking ancestry relationship into account. This is
+useful to produce human-readable log output.
+
+Commits which are stated with a preceding '{caret}' cause listing to stop at
+that point. Their parents are implied. "git-rev-list foo bar {caret}baz" thus
+means "list all the commits which are included in 'foo' and 'bar', but
+not in 'baz'".
+
+A special notation <commit1>..<commit2> can be used as a
+short-hand for {caret}<commit1> <commit2>.
+
+
+OPTIONS
+-------
+--pretty::
+ Print the contents of the commit changesets in human-readable form.
+
+--header::
+ Print the contents of the commit in raw-format; each
+ record is separated with a NUL character.
+
+--objects::
+ Print the object IDs of any object referenced by the listed commits.
+ 'git-rev-list --objects foo ^bar' thus means "send me all object IDs
+ which I need to download if I have the commit object 'bar', but
+ not 'foo'".
+
+--unpacked::
+ Only useful with `--objects`; print the object IDs that
+ are not in packs.
+
+--bisect::
+ Limit output to the one commit object which is roughly halfway
+ between the included and excluded commits. Thus, if 'git-rev-list
+ --bisect foo ^bar ^baz' outputs 'midpoint', the output
+ of 'git-rev-list foo ^midpoint' and 'git-rev-list midpoint
+ ^bar ^baz' would be of roughly the same length. Finding the change
+ which introduces a regression is thus reduced to a binary search:
+ repeatedly generate and test new 'midpoint's until the commit chain
+ is of length one.
+
+--max-count::
+ Limit the number of commits output.
+
+--max-age=timestamp, --min-age=timestamp::
+ Limit the commits output to specified time range.
+
+--sparse::
+ When optional paths are given, the command outputs only
+ the commits that changes at least one of them, and also
+ ignores merges that do not touch the given paths. This
+ flag makes the command output all eligible commits
+ (still subject to count and age limitation), but apply
+ merge simplification nevertheless.
+
+--all::
+ Pretend as if all the refs in `$GIT_DIR/refs/` are
+ listed on the command line as <commit>.
+
+--topo-order::
+ By default, the commits are shown in reverse
+ chronological order. This option makes them appear in
+ topological order (i.e. descendant commits are shown
+ before their parents).
+
+--merge-order::
+ When specified the commit history is decomposed into a unique
+ sequence of minimal, non-linear epochs and maximal, linear epochs.
+ Non-linear epochs are then linearised by sorting them into merge
+ order, which is described below.
++
+Maximal, linear epochs correspond to periods of sequential development.
+Minimal, non-linear epochs correspond to periods of divergent development
+followed by a converging merge. The theory of epochs is described in more
+detail at
+link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
++
+The merge order for a non-linear epoch is defined as a linearisation for which
+the following invariants are true:
++
+ 1. if a commit P is reachable from commit N, commit P sorts after commit N
+ in the linearised list.
+ 2. if Pi and Pj are any two parents of a merge M (with i < j), then any
+ commit N, such that N is reachable from Pj but not reachable from Pi,
+ sorts before all commits reachable from Pi.
++
+Invariant 1 states that later commits appear before earlier commits they are
+derived from.
++
+Invariant 2 states that commits unique to "later" parents in a merge, appear
+before all commits from "earlier" parents of a merge.
+
+--show-breaks::
+ Each item of the list is output with a 2-character prefix consisting
+ of one of: (|), (^), (=) followed by a space.
++
+Commits marked with (=) represent the boundaries of minimal, non-linear epochs
+and correspond either to the start of a period of divergent development or to
+the end of such a period.
++
+Commits marked with (|) are direct parents of commits immediately preceding
+the marked commit in the list.
++
+Commits marked with (^) are not parents of the immediately preceding commit.
+These "breaks" represent necessary discontinuities implied by trying to
+represent an arbtirary DAG in a linear form.
++
+`--show-breaks` is only valid if `--merge-order` is also specified.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
new file mode 100644
index 000000000..431b8f6e0
--- /dev/null
+++ b/Documentation/git-rev-parse.txt
@@ -0,0 +1,174 @@
+git-rev-parse(1)
+================
+
+NAME
+----
+git-rev-parse - Pick out and massage parameters.
+
+
+SYNOPSIS
+--------
+'git-rev-parse' [ --option ] <args>...
+
+DESCRIPTION
+-----------
+
+Many git Porcelainish commands take mixture of flags
+(i.e. parameters that begin with a dash '-') and parameters
+meant for underlying `git-rev-list` command they use internally
+and flags and parameters for other commands they use as the
+downstream of `git-rev-list`. This command is used to
+distinguish between them.
+
+
+OPTIONS
+-------
+--revs-only::
+ Do not output flags and parameters not meant for
+ `git-rev-list` command.
+
+--no-revs::
+ Do not output flags and parameters meant for
+ `git-rev-list` command.
+
+--flags::
+ Do not output non-flag parameters.
+
+--no-flags::
+ Do not output flag parameters.
+
+--default <arg>::
+ If there is no parameter given by the user, use `<arg>`
+ instead.
+
+--verify::
+ The parameter given must be usable as a single, valid
+ object name. Otherwise barf and abort.
+
+--sq::
+ Usually the output is made one line per flag and
+ parameter. This option makes output a single line,
+ properly quoted for consumption by shell. Useful when
+ you expect your parameter to contain whitespaces and
+ newlines (e.g. when using pickaxe `-S` with
+ `git-diff-\*`).
+
+--not::
+ When showing object names, prefix them with '{caret}' and
+ strip '{caret}' prefix from the object names that already have
+ one.
+
+--symbolic::
+ Usually the object names are output in SHA1 form (with
+ possible '{caret}' prefix); this option makes them output in a
+ form as close to the original input as possible.
+
+
+--all::
+ Show all refs found in `$GIT_DIR/refs`.
+
+--show-prefix::
+ When the command is invoked from a directory show the
+ path of the current directory relative to the top-level
+ directory.
+
+--since=datestring, --after=datestring::
+ Parses the date string, and outputs corresponding
+ --max-age= parameter for git-rev-list command.
+
+--until=datestring, --before=datestring::
+ Parses the date string, and outputs corresponding
+ --min-age= parameter for git-rev-list command.
+
+<args>...::
+ Flags and parameters to be parsed.
+
+
+SPECIFYING REVISIONS
+--------------------
+
+A revision parameter typically, but not necessarily, names a
+commit object. They use what is called an 'extended SHA1'
+syntax.
+
+* The full SHA1 object name (40-byte hexadecimal string), or
+ a substring of such that is unique within the repository.
+ E.g. dae86e1950b1277e545cee180551750029cfe735 and dae86e both
+ name the same commit object if there are no other object in
+ your repository whose object name starts with dae86e.
+
+* A symbolic ref name. E.g. 'master' typically means the commit
+ object referenced by $GIT_DIR/refs/heads/master. If you
+ happen to have both heads/master and tags/master, you can
+ explicitly say 'heads/master' to tell git which one you mean.
+
+* A suffix '{caret}' to a revision parameter means the first parent of
+ that commit object. '{caret}<n>' means the <n>th parent (i.e.
+ 'rev{caret}'
+ is equivalent to 'rev{caret}1'). As a special rule,
+ 'rev{caret}0' means the commit itself and is used when 'rev' is the
+ object name of a tag object that refers to a commit object.
+
+* A suffix '~<n>' to a revision parameter means the commit
+ object that is the <n>th generation grand-parent of the named
+ commit object, following only the first parent. I.e. rev~3 is
+ equivalent to rev{caret}{caret}{caret} which is equivalent to\
+ rev{caret}1{caret}1{caret}1.
+
+* A suffix '{caret}' followed by an object type name enclosed in
+ brace pair (e.g. `v0.99.8{caret}\{commit\}`) means the object
+ could be a tag, and dereference the tag recursively until an
+ object of that type is found or the object cannot be
+ dereferenced anymore (in which case, barf). `rev{caret}0`
+ introduced earlier is a short-hand for `rev{caret}\{commit\}`.
+
+* A suffix '{caret}' followed by an empty brace pair
+ (e.g. `v0.99.8{caret}\{\}`) means the object could be a tag,
+ and dereference the tag recursively until a non-tag object is
+ found.
+
+'git-rev-parse' also accepts a prefix '{caret}' to revision parameter,
+which is passed to 'git-rev-list'. Two revision parameters
+concatenated with '..' is a short-hand for writing a range
+between them. I.e. 'r1..r2' is equivalent to saying '{caret}r1 r2'
+
+Here is an illustration, by Jon Loeliger. Both node B and C are
+a commit parents of commit node A. Parent commits are ordered
+left-to-right.
+
+ G H I J
+ \ / \ /
+ D E F
+ \ | /
+ \ | /
+ \|/
+ B C
+ \ /
+ \ /
+ A
+
+ A = = A^0
+ B = A^ = A^1 = A~1
+ C = A^2 = A^2
+ D = A^^ = A^1^1 = A~2
+ E = B^2 = A^^2
+ F = B^3 = A^^3
+ G = A^^^ = A^1^1^1 = A~3
+ H = D^2 = B^^2 = A^^^2 = A~2^2
+ I = F^ = B^3^ = A^^3^
+ J = F^2 = B^3^2 = A^^3^2
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt
new file mode 100644
index 000000000..feebd81da
--- /dev/null
+++ b/Documentation/git-revert.txt
@@ -0,0 +1,48 @@
+git-revert(1)
+=============
+
+NAME
+----
+git-revert - Revert an existing commit.
+
+SYNOPSIS
+--------
+'git-revert' [-n] <commit>
+
+DESCRIPTION
+-----------
+Given one existing commit, revert the change the patch introduces, and record a
+new commit that records it. This requires your working tree to be clean (no
+modifications from the HEAD commit).
+
+OPTIONS
+-------
+<commit>::
+ Commit to revert.
+
+-n::
+ Usually the command automatically creates a commit with
+ a commit log message stating which commit was reverted.
+ This flag applies the change necessary to revert the
+ named commit to your working tree, but does not make the
+ commit. In addition, when this option is used, your
+ working tree does not have to match the HEAD commit.
+ The revert is done against the beginning state of your
+ working tree.
++
+This is useful when reverting more than one commits'
+effect to your working tree in a row.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
new file mode 100644
index 000000000..b9bec55e5
--- /dev/null
+++ b/Documentation/git-send-email.txt
@@ -0,0 +1,80 @@
+git-send-email(1)
+=================
+
+NAME
+----
+git-send-email - Send a collection of patches as emails
+
+
+SYNOPSIS
+--------
+'git-send-email' [options] <file|directory> [... file|directory]
+
+
+
+DESCRIPTION
+-----------
+Takes the patches given on the command line and emails them out.
+
+The header of the email is configurable by command line options. If not
+specified on the command line, the user will be prompted with a ReadLine
+enabled interface to provide the necessary information.
+
+OPTIONS
+-------
+The options available are:
+
+--to::
+ Specify the primary recipient of the emails generated.
+ Generally, this will be the upstream maintainer of the
+ project involved.
+
+--from::
+ Specify the sender of the emails. This will default to
+ the value GIT_COMMITTER_IDENT, as returned by "git-var -l".
+ The user will still be prompted to confirm this entry.
+
+--compose::
+ Use \$EDITOR to edit an introductory message for the
+ patch series.
+
+--subject::
+ Specify the initial subject of the email thread.
+ Only necessary if --compose is also set. If --compose
+ is not set, this will be prompted for.
+
+--in-reply-to::
+ Specify the contents of the first In-Reply-To header.
+ Subsequent emails will refer to the previous email
+ instead of this if --chain-reply-to is set (the default)
+ Only necessary if --compose is also set. If --compose
+ is not set, this will be prompted for.
+
+--chain-reply-to, --no-chain-reply-to::
+ If this is set, each email will be sent as a reply to the previous
+ email sent. If disabled with "--no-chain-reply-to", all emails after
+ the first will be sent as replies to the first email sent. When using
+ this, it is recommended that the first file given be an overview of the
+ entire patch series.
+ Default is --chain-reply-to
+
+--smtp-server::
+ If set, specifies the outgoing SMTP server to use. Defaults to
+ localhost.
+
+
+Author
+------
+Written by Ryan Anderson <ryan@michonline.com>
+
+git-send-email is originally based upon
+send_lots_of_email.pl by Greg Kroah-Hartman.
+
+Documentation
+--------------
+Documentation by Ryan Anderson
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
new file mode 100644
index 000000000..577f06a21
--- /dev/null
+++ b/Documentation/git-send-pack.txt
@@ -0,0 +1,110 @@
+git-send-pack(1)
+================
+
+NAME
+----
+git-send-pack - Push missing objects packed.
+
+
+SYNOPSIS
+--------
+'git-send-pack' [--all] [--force] [--exec=<git-receive-pack>] [<host>:]<directory> [<ref>...]
+
+DESCRIPTION
+-----------
+Invokes 'git-receive-pack' on a possibly remote repository, and
+updates it from the current repository, sending named refs.
+
+
+OPTIONS
+-------
+--exec=<git-receive-pack>::
+ Path to the 'git-receive-pack' program on the remote
+ end. Sometimes useful when pushing to a remote
+ repository over ssh, and you do not have the program in
+ a directory on the default $PATH.
+
+--all::
+ Instead of explicitly specifying which refs to update,
+ update all refs that locally exist.
+
+--force::
+ Usually, the command refuses to update a remote ref that
+ is not an ancestor of the local ref used to overwrite it.
+ This flag disables the check. What this means is that
+ the remote repository can lose commits; use it with
+ care.
+
+<host>::
+ A remote host to house the repository. When this
+ part is specified, 'git-receive-pack' is invoked via
+ ssh.
+
+<directory>::
+ The repository to update.
+
+<ref>...:
+ The remote refs to update.
+
+
+Specifying the Refs
+-------------------
+
+There are three ways to specify which refs to update on the
+remote end.
+
+With '--all' flag, all refs that exist locally are transfered to
+the remote side. You cannot specify any '<ref>' if you use
+this flag.
+
+Without '--all' and without any '<ref>', the refs that exist
+both on the local side and on the remote side are updated.
+
+When one or more '<ref>' are specified explicitly, it can be either a
+single pattern, or a pair of such pattern separated by a colon
+":" (this means that a ref name cannot have a colon in it). A
+single pattern '<name>' is just a shorthand for '<name>:<name>'.
+
+Each pattern pair consists of the source side (before the colon)
+and the destination side (after the colon). The ref to be
+pushed is determined by finding a match that matches the source
+side, and where it is pushed is determined by using the
+destination side.
+
+ - It is an error if <src> does not match exactly one of the
+ local refs.
+
+ - It is an error if <dst> matches more than one remote refs.
+
+ - If <dst> does not match any remote ref, either
+
+ * it has to start with "refs/"; <dst> is used as the
+ destination literally in this case.
+
+ * <src> == <dst> and the ref that matched the <src> must not
+ exist in the set of remote refs; the ref matched <src>
+ locally is used as the name of the destination.
+
+Without '--force', the <src> ref is stored at the remote only if
+<dst> does not exist, or <dst> is a proper subset (i.e. an
+ancestor) of <src>. This check, known as "fast forward check",
+is performed in order to avoid accidentally overwriting the
+remote ref and lose other peoples' commits from there.
+
+With '--force', the fast forward check is disabled for all refs.
+
+Optionally, a <ref> parameter can be prefixed with a plus '+' sign
+to disable the fast-forward check only on that ref.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt
new file mode 100644
index 000000000..6ef59acf5
--- /dev/null
+++ b/Documentation/git-sh-setup.txt
@@ -0,0 +1,35 @@
+git-sh-setup(1)
+===============
+
+NAME
+----
+git-sh-setup - Common git shell script setup code.
+
+SYNOPSIS
+--------
+'git-sh-setup'
+
+DESCRIPTION
+-----------
+
+Sets up the normal git environment variables and a few helper functions
+(currently just "die()"), and returns ok if it all looks like a git archive.
+So, to make the rest of the git scripts more careful and readable,
+use it as follows:
+
+-------------------------------------------------
+. git-sh-setup || die "Not a git archive"
+-------------------------------------------------
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt
new file mode 100644
index 000000000..3f4d804cc
--- /dev/null
+++ b/Documentation/git-shell.txt
@@ -0,0 +1,35 @@
+git-shell(1)
+============
+
+NAME
+----
+git-shell - Restricted login shell for GIT over SSH only
+
+
+SYNOPSIS
+--------
+'git-shell -c <command> <argument>'
+
+DESCRIPTION
+-----------
+This is meant to be used as a login shell for SSH accounts you want
+to restrict to GIT pull/push access only. It permits execution only
+of server-side GIT commands implementing the pull/push functionality.
+The commands can be executed only by the '-c' option; the shell is not
+interactive.
+
+Currently, only the `git-receive-pack` and `git-upload-pack` commands
+are permitted to be called, with a single required argument.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Petr Baudis and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt
new file mode 100644
index 000000000..65ca77fbf
--- /dev/null
+++ b/Documentation/git-shortlog.txt
@@ -0,0 +1,30 @@
+git-shortlog(1)
+===============
+
+NAME
+----
+git-shortlog - Summarize 'git log' output.
+
+
+SYNOPSIS
+--------
+'git-log --pretty=short | git shortlog'
+
+DESCRIPTION
+-----------
+Summarizes 'git log' output in a format suitable for inclusion
+in release announcements.
+
+
+Author
+------
+Written by Jeff Garzik <jgarzik@pobox.com>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt
new file mode 100644
index 000000000..c6c97b21c
--- /dev/null
+++ b/Documentation/git-show-branch.txt
@@ -0,0 +1,113 @@
+git-show-branch(1)
+==================
+
+NAME
+----
+git-show-branch - Show branches and their commits.
+
+SYNOPSIS
+--------
+'git-show-branch [--all] [--heads] [--tags] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] <reference>...'
+
+DESCRIPTION
+-----------
+Shows the head commits from the named <reference> (or all refs under
+$GIT_DIR/refs/heads), and displays concise list of commit logs
+to show their relationship semi-visually.
+
+OPTIONS
+-------
+<reference>::
+ Name of the reference under $GIT_DIR/refs/.
+
+--all --heads --tags::
+ Show all refs under $GIT_DIR/refs, $GIT_DIR/refs/heads,
+ and $GIT_DIR/refs/tags, respectively.
+
+--more=<n>::
+ Usually the command stops output upon showing the commit
+ that is the common ancestor of all the branches. This
+ flag tells the command to go <n> more common commits
+ beyond that. When <n> is negative, display only the
+ <reference>s given, without showing the commit ancestry
+ tree.
+
+--list::
+ Synomym to `--more=-1`
+
+--merge-base::
+ Instead of showing the commit list, just act like the
+ 'git-merge-base -a' command, except that it can accept
+ more than two heads.
+
+--independent::
+ Among the <reference>s given, display only the ones that
+ cannot be reached from any other <reference>.
+
+--no-name::
+ Do not show naming strings for each commit.
+
+--sha1-name::
+ Instead of naming the commits using the path to reach
+ them from heads (e.g. "master~2" to mean the grandparent
+ of "master"), name them with the unique prefix of their
+ object names.
+
+Note that --more, --list, --independent and --merge-base options
+are mutually exclusive.
+
+
+OUTPUT
+------
+Given N <references>, the first N lines are the one-line
+description from their commit message. The branch head that is
+pointed at by $GIT_DIR/HEAD is prefixed with an asterisk '*'
+character while other heads are prefixed with a '!' character.
+
+Following these N lines, one-line log for each commit is
+displayed, indented N places. If a commit is on the I-th
+branch, the I-th indentation character shows a '+' sign;
+otherwise it shows a space. Each commit shows a short name that
+can be used as an exended SHA1 to name that commit.
+
+The following example shows three branches, "master", "fixes"
+and "mhf":
+
+------------------------------------------------
+$ git show-branch master fixes mhf
+! [master] Add 'git show-branch'.
+ ! [fixes] Introduce "reset type" flag to "git reset"
+ ! [mhf] Allow "+remote:local" refspec to cause --force when fetching.
+---
+ + [mhf] Allow "+remote:local" refspec to cause --force when fetching.
+ + [mhf~1] Use git-octopus when pulling more than one heads.
+ + [fixes] Introduce "reset type" flag to "git reset"
+ + [mhf~2] "git fetch --force".
+ + [mhf~3] Use .git/remote/origin, not .git/branches/origin.
+ + [mhf~4] Make "git pull" and "git fetch" default to origin
+ + [mhf~5] Infamous 'octopus merge'
+ + [mhf~6] Retire git-parse-remote.
+ + [mhf~7] Multi-head fetch.
+ + [mhf~8] Start adding the $GIT_DIR/remotes/ support.
++++ [master] Add 'git show-branch'.
+------------------------------------------------
+
+These three branches all forked from a common commit, [master],
+whose commit message is "Add 'git show-branch'. "fixes" branch
+adds one commit 'Introduce "reset type"'. "mhf" branch has many
+other commits.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt
new file mode 100644
index 000000000..be09b62be
--- /dev/null
+++ b/Documentation/git-show-index.txt
@@ -0,0 +1,35 @@
+git-show-index(1)
+=================
+
+NAME
+----
+git-show-index - Show packed archive index
+
+
+SYNOPSIS
+--------
+'git-show-index' < idx-file
+
+
+DESCRIPTION
+-----------
+Reads given idx file for packed git archive created with
+git-pack-objects command, and dumps its contents.
+
+The information it outputs is subset of what you can get from
+'git-verify-pack -v'; this command only shows the packfile
+offset and SHA1 of each object.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-ssh-fetch.txt b/Documentation/git-ssh-fetch.txt
new file mode 100644
index 000000000..b7116b30e
--- /dev/null
+++ b/Documentation/git-ssh-fetch.txt
@@ -0,0 +1,51 @@
+git-ssh-fetch(1)
+================
+
+NAME
+----
+git-ssh-fetch - Pulls from a remote repository over ssh connection
+
+
+
+SYNOPSIS
+--------
+'git-ssh-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
+
+DESCRIPTION
+-----------
+Pulls from a remote repository over ssh connection, invoking
+git-ssh-upload on the other end. It functions identically to
+git-ssh-upload, aside from which end you run it on.
+
+
+OPTIONS
+-------
+commit-id::
+ Either the hash or the filename under [URL]/refs/ to
+ pull.
+
+-c::
+ Get the commit objects.
+-t::
+ Get trees associated with the commit objects.
+-a::
+ Get all the objects.
+-v::
+ Report what is downloaded.
+-w::
+ Writes the commit-id into the filename under $GIT_DIR/refs/ on
+ the local end after the transfer is complete.
+
+
+Author
+------
+Written by Daniel Barkalow <barkalow@iabervon.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-ssh-upload.txt b/Documentation/git-ssh-upload.txt
new file mode 100644
index 000000000..702674e45
--- /dev/null
+++ b/Documentation/git-ssh-upload.txt
@@ -0,0 +1,47 @@
+git-ssh-upload(1)
+=================
+
+NAME
+----
+git-ssh-upload - Pushes to a remote repository over ssh connection
+
+
+SYNOPSIS
+--------
+'git-ssh-upload' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] commit-id url
+
+DESCRIPTION
+-----------
+Pushes from a remote repository over ssh connection, invoking
+git-ssh-fetch on the other end. It functions identically to
+git-ssh-fetch, aside from which end you run it on.
+
+OPTIONS
+-------
+commit-id::
+ Id of commit to push.
+
+-c::
+ Get the commit objects.
+-t::
+ Get tree associated with the requested commit object.
+-a::
+ Get all the objects.
+-v::
+ Report what is uploaded.
+-w::
+ Writes the commit-id into the filename under [URL]/refs/ on
+ the remote end after the transfer is complete.
+
+Author
+------
+Written by Daniel Barkalow <barkalow@iabervon.org>
+
+Documentation
+--------------
+Documentation by Daniel Barkalow
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt
new file mode 100644
index 000000000..753fc0866
--- /dev/null
+++ b/Documentation/git-status.txt
@@ -0,0 +1,45 @@
+git-status(1)
+=============
+
+NAME
+----
+git-status - Show working tree status.
+
+
+SYNOPSIS
+--------
+'git-status'
+
+DESCRIPTION
+-----------
+Examines paths in the working tree that has changes unrecorded
+to the index file, and changes between the index file and the
+current HEAD commit. The former paths are what you _could_
+commit by running 'git-update-index' before running 'git
+commit', and the latter paths are what you _would_ commit by
+running 'git commit'.
+
+If there is no path that is different between the index file and
+the current HEAD commit, the command exits with non-zero
+status.
+
+
+OUTPUT
+------
+The output from this command is designed to be used as a commit
+template comments, and all the output lines are prefixed with '#'.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt
new file mode 100644
index 000000000..528a1b6ce
--- /dev/null
+++ b/Documentation/git-stripspace.txt
@@ -0,0 +1,33 @@
+git-stripspace(1)
+=================
+
+NAME
+----
+git-stripspace - Filter out empty lines.
+
+
+SYNOPSIS
+--------
+'git-stripspace' < <stream>
+
+DESCRIPTION
+-----------
+Remove multiple empty lines, and empty lines at beginning and end.
+
+OPTIONS
+-------
+<stream>::
+ Byte stream to act on.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt
new file mode 100644
index 000000000..fcc79fa93
--- /dev/null
+++ b/Documentation/git-svnimport.txt
@@ -0,0 +1,137 @@
+git-svnimport(1)
+================
+v0.1, July 2005
+
+NAME
+----
+git-svnimport - Import a SVN repository into git
+
+
+SYNOPSIS
+--------
+'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
+ [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_nr_changes]
+ [ -b branch_subdir ] [ -t trunk_subdir ] [ -T tag_subdir ]
+ [ -s start_chg ] [ -m ] [ -M regex ]
+ <SVN_repository_URL> [ <path> ]
+
+
+DESCRIPTION
+-----------
+Imports a SVN repository into git. It will either create a new
+repository, or incrementally import into an existing one.
+
+SVN access is done by the SVN:: Perl module.
+
+git-svnimport assumes that SVN repositories are organized into one
+"trunk" directory where the main development happens, "branch/FOO"
+directories for branches, and "/tags/FOO" directories for tags.
+Other subdirectories are ignored.
+
+git-svnimport creates a file ".git/svn2git", which is required for
+incremental SVN imports.
+
+OPTIONS
+-------
+-C <target-dir>::
+ The GIT repository to import to. If the directory doesn't
+ exist, it will be created. Default is the current directory.
+
+-s <start_rev>::
+ Start importing at this SVN change number. The default is 1.
++
+When importing incementally, you might need to edit the .git/svn2git file.
+
+-i::
+ Import-only: don't perform a checkout after importing. This option
+ ensures the working directory and index remain untouched and will
+ not create them if they do not exist.
+
+-t <trunk_subdir>::
+ Name the SVN trunk. Default "trunk".
+
+-T <tag_subdir>::
+ Name the SVN subdirectory for tags. Default "tags".
+
+-b <branch_subdir>::
+ Name the SVN subdirectory for branches. Default "branches".
+
+-o <branch-for-HEAD>::
+ The 'trunk' branch from SVN is imported to the 'origin' branch within
+ the git repository. Use this option if you want to import into a
+ different branch.
+
+-m::
+ Attempt to detect merges based on the commit message. This option
+ will enable default regexes that try to capture the name source
+ branch name from the commit message.
+
+-M <regex>::
+ Attempt to detect merges based on the commit message with a custom
+ regex. It can be used with -m to also see the default regexes.
+ You must escape forward slashes.
+
+-l <max_num_changes>::
+ Limit the number of SVN changesets we pull before quitting.
+ This option is necessary because the SVN library has serious memory
+ leaks; the recommended value for nontrivial imports is 100.
+
+ git-svnimport will still exit with a zero exit code. You can check
+ the size of the file ".git/svn2git" to determine whether to call
+ the importer again.
+
+-v::
+ Verbosity: let 'svnimport' report what it is doing.
+
+-d::
+ Use direct HTTP requests if possible. The "<path>" argument is used
+ only for retrieving the SVN logs; the path to the contents is
+ included in the SVN log.
+
+-D::
+ Use direct HTTP requests if possible. The "<path>" argument is used
+ for retrieving the logs, as well as for the contents.
++
+There's no safe way to automatically find out which of these options to
+use, so you need to try both. Usually, the one that's wrong will die
+with a 40x error pretty quickly.
+
+<SVN_repository_URL>::
+ The URL of the SVN module you want to import. For local
+ repositories, use "file:///absolute/path".
++
+If you're using the "-d" or "-D" option, this is the URL of the SVN
+repository itself; it usually ends in "/svn".
+
+<SVN_repository_URL>::
+ The URL of the SVN module you want to import. For local
+ repositories, use "file:///absolute/path".
+
+<path>
+ The path to the module you want to check out.
+
+-h::
+ Print a short usage message and exit.
+
+OUTPUT
+------
+If '-v' is specified, the script reports what it is doing.
+
+Otherwise, success is indicated the Unix way, i.e. by simply exiting with
+a zero exit status.
+
+Author
+------
+Written by Matthias Urlichs <smurf@smurf.noris.de>, with help from
+various participants of the git-list <git@vger.kernel.org>.
+
+Based on a cvs2git script by the same author.
+
+Documentation
+--------------
+Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
new file mode 100644
index 000000000..68ac6a65d
--- /dev/null
+++ b/Documentation/git-symbolic-ref.txt
@@ -0,0 +1,52 @@
+git-symbolic-ref(1)
+===================
+
+NAME
+----
+git-symbolic-ref - read and modify symbolic refs
+
+SYNOPSIS
+--------
+'git-symbolic-ref' <name> [<ref>]
+
+DESCRIPTION
+-----------
+Given one argument, reads which branch head the given symbolic
+ref refers to and outputs its path, relative to the `.git/`
+directory. Typically you would give `HEAD` as the <name>
+argument to see on which branch your working tree is on.
+
+Give two arguments, create or update a symbolic ref <name> to
+point at the given branch <ref>.
+
+Traditionally, `.git/HEAD` is a symlink pointing at
+`refs/heads/master`. When we want to switch to another branch,
+we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we want
+to find out which branch we are on, we did `readlink .git/HEAD`.
+This was fine, and internally that is what still happens by
+default, but on platforms that do not have working symlinks,
+or that do not have the `readlink(1)` command, this was a bit
+cumbersome. On some platforms, `ln -sf` does not even work as
+advertised (horrors).
+
+A symbolic ref can be a regular file that stores a string that
+begins with `ref: refs/`. For example, your `.git/HEAD` *can*
+be a regular file whose contents is `ref: refs/heads/master`.
+This can be used on a filesystem that does not support symbolic
+links. Instead of doing `readlink .git/HEAD`, `git-symbolic-ref
+HEAD` can be used to find out which branch we are on. To point
+the HEAD to `newbranch`, instead of `ln -sf refs/heads/newbranch
+.git/HEAD`, `git-symbolic-ref HEAD refs/heads/newbranch` can be
+used.
+
+Currently, .git/HEAD uses a regular file symbolic ref on Cygwin,
+and everywhere else it is implemented as a symlink. This can be
+changed at compilation time.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
new file mode 100644
index 000000000..95de436c1
--- /dev/null
+++ b/Documentation/git-tag.txt
@@ -0,0 +1,47 @@
+git-tag(1)
+==========
+
+NAME
+----
+git-tag - Create a tag object signed with GPG
+
+
+SYNOPSIS
+--------
+'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <name> [<head>]
+
+DESCRIPTION
+-----------
+Adds a 'tag' reference in .git/refs/tags/
+
+Unless `-f` is given, the tag must not yet exist in
+`.git/refs/tags/` directory.
+
+If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
+creates a 'tag' object, and requires the tag message. Unless
+`-m <msg>` is given, an editor is started for the user to type
+in the tag message.
+
+Otherwise just the SHA1 object name of the commit object is
+written (i.e. an lightweight tag).
+
+A GnuPG signed tag object will be created when `-s` or `-u
+<key-id>` is used. When `-u <key-id>` is not used, the
+committer identity for the current user is used to find the
+GnuPG key for signing.
+
+`-d <tag>` deletes the tag.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>,
+Junio C Hamano <junkio@cox.net> and Chris Wright <chrisw@osdl.org>.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt
new file mode 100644
index 000000000..2139b6ff8
--- /dev/null
+++ b/Documentation/git-tar-tree.txt
@@ -0,0 +1,38 @@
+git-tar-tree(1)
+===============
+
+NAME
+----
+git-tar-tree - Creates a tar archive of the files in the named tree
+
+
+SYNOPSIS
+--------
+'git-tar-tree' <tree-ish> [ <base> ]
+
+DESCRIPTION
+-----------
+Creates a tar archive containing the tree structure for the named tree.
+When <base> is specified it is added as a leading path to the files in the
+generated tar archive.
+
+git-tar-tree behaves differently when given a tree ID versus when given
+a commit ID or tag ID. In the first case the current time is used as
+modification time of each file in the archive. In the latter case the
+commit time as recorded in the referenced commit object is used instead.
+Additionally the commit ID is stored in a global extended pax header.
+It can be extracted using git-get-tar-commit-id.
+
+
+Author
+------
+Written by Rene Scharfe.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-unpack-file.txt b/Documentation/git-unpack-file.txt
new file mode 100644
index 000000000..213dc8196
--- /dev/null
+++ b/Documentation/git-unpack-file.txt
@@ -0,0 +1,36 @@
+git-unpack-file(1)
+==================
+
+NAME
+----
+git-unpack-file - Creates a temporary file with a blob's contents
+
+
+
+SYNOPSIS
+--------
+'git-unpack-file' <blob>
+
+DESCRIPTION
+-----------
+Creates a file holding the contents of the blob specified by sha1. It
+returns the name of the temporary file in the following format:
+ .merge_file_XXXXX
+
+OPTIONS
+-------
+<blob>::
+ Must be a blob id
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt
new file mode 100644
index 000000000..31ea34d22
--- /dev/null
+++ b/Documentation/git-unpack-objects.txt
@@ -0,0 +1,42 @@
+git-unpack-objects(1)
+=====================
+
+NAME
+----
+git-unpack-objects - Unpack objects from a packed archive.
+
+
+SYNOPSIS
+--------
+'git-unpack-objects' [-n] [-q] <pack-file
+
+
+DESCRIPTION
+-----------
+Reads a packed archive (.pack) from the standard input, and
+expands the objects contained in the pack into "one-file
+one-object" format in $GIT_OBJECT_DIRECTORY.
+
+OPTIONS
+-------
+-n::
+ Only list the objects that would be unpacked, don't actually unpack
+ them.
+
+-q::
+ The command usually shows percentage progress. This
+ flag suppresses it.
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+-------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
new file mode 100644
index 000000000..fdcb8bea7
--- /dev/null
+++ b/Documentation/git-update-index.txt
@@ -0,0 +1,166 @@
+git-update-index(1)
+===================
+
+NAME
+----
+git-update-index - Modifies the index or directory cache
+
+
+SYNOPSIS
+--------
+'git-update-index'
+ [--add] [--remove | --force-remove] [--replace]
+ [--refresh [-q] [--unmerged] [--ignore-missing]]
+ [--cacheinfo <mode> <object> <file>]\*
+ [--chmod=(+|-)x]
+ [--info-only] [--index-info]
+ [-z] [--stdin]
+ [--verbose]
+ [--] [<file>]\*
+
+DESCRIPTION
+-----------
+Modifies the index or directory cache. Each file mentioned is updated
+into the index and any 'unmerged' or 'needs updating' state is
+cleared.
+
+The way "git-update-index" handles files it is told about can be modified
+using the various options:
+
+OPTIONS
+-------
+--add::
+ If a specified file isn't in the index already then it's
+ added.
+ Default behaviour is to ignore new files.
+
+--remove::
+ If a specified file is in the index but is missing then it's
+ removed.
+ Default behaviour is to ignore removed file.
+
+--refresh::
+ Looks at the current index and checks to see if merges or
+ updates are needed by checking stat() information.
+
+-q::
+ Quiet. If --refresh finds that the index needs an update, the
+ default behavior is to error out. This option makes
+ git-update-index continue anyway.
+
+--unmerged::
+ If --refresh finds unmerged changes in the index, the default
+ behavior is to error out. This option makes git-update-index
+ continue anyway.
+
+--ignore-missing::
+ Ignores missing files during a --refresh
+
+--cacheinfo <mode> <object> <path>::
+ Directly insert the specified info into the index.
+
+--index-info::
+ Read index information from stdin.
+
+--chmod=(+|-)x::
+ Set the execute permissions on the updated files.
+
+--info-only::
+ Do not create objects in the object database for all
+ <file> arguments that follow this flag; just insert
+ their object IDs into the index.
+
+--force-remove::
+ Remove the file from the index even when the working directory
+ still has such a file. (Implies --remove.)
+
+--replace::
+ By default, when a file `path` exists in the index,
+ git-update-index refuses an attempt to add `path/file`.
+ Similarly if a file `path/file` exists, a file `path`
+ cannot be added. With --replace flag, existing entries
+ that conflicts with the entry being added are
+ automatically removed with warning messages.
+
+--stdin::
+ Instead of taking list of paths from the command line,
+ read list of paths from the standard input. Paths are
+ separated by LF (i.e. one path per line) by default.
+
+--verbose::
+ Report what is being added and removed from index.
+
+-z::
+ Only meaningful with `--stdin`; paths are separated with
+ NUL character instead of LF.
+
+--::
+ Do not interpret any more arguments as options.
+
+<file>::
+ Files to act on.
+ Note that files beginning with '.' are discarded. This includes
+ `./file` and `dir/./file`. If you don't want this, then use
+ cleaner names.
+ The same applies to directories ending '/' and paths with '//'
+
+Using --refresh
+---------------
+'--refresh' does not calculate a new sha1 file or bring the index
+up-to-date for mode/content changes. But what it *does* do is to
+"re-match" the stat information of a file with the index, so that you
+can refresh the index for a file that hasn't been changed but where
+the stat entry is out of date.
+
+For example, you'd want to do this after doing a "git-read-tree", to link
+up the stat index details with the proper files.
+
+Using --cacheinfo or --info-only
+--------------------------------
+'--cacheinfo' is used to register a file that is not in the
+current working directory. This is useful for minimum-checkout
+merging.
+
+To pretend you have a file with mode and sha1 at path, say:
+
+ $ git-update-index --cacheinfo mode sha1 path
+
+'--info-only' is used to register files without placing them in the object
+database. This is useful for status-only repositories.
+
+Both '--cacheinfo' and '--info-only' behave similarly: the index is updated
+but the object database isn't. '--cacheinfo' is useful when the object is
+in the database but the file isn't available locally. '--info-only' is
+useful when the file is available, but you do not wish to update the
+object database.
+
+Examples
+--------
+To update and refresh only the files already checked out:
+
+ git-checkout-index -n -f -a && git-update-index --ignore-missing --refresh
+
+
+Configuration
+-------------
+
+The command honors `core.filemode` configuration variable. If
+your repository is on an filesystem whose executable bits are
+unreliable, this should be set to 'false'. This causes the
+command to ignore differences in file modes recorded in the
+index and the file mode on the filesystem if they differ only on
+executable bit. On such an unfortunate filesystem, you may
+need to use `git-update-index --chmod=`.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
new file mode 100644
index 000000000..69715aa06
--- /dev/null
+++ b/Documentation/git-update-ref.txt
@@ -0,0 +1,58 @@
+git-update-ref(1)
+=================
+
+NAME
+----
+git-update-ref - update the object name stored in a ref safely
+
+SYNOPSIS
+--------
+`git-update-ref` <ref> <newvalue> [<oldvalue>]
+
+DESCRIPTION
+-----------
+Given two arguments, stores the <newvalue> in the <ref>, possibly
+dereferencing the symbolic refs. E.g. `git-update-ref HEAD
+<newvalue>` updates the current branch head to the new object.
+
+Given three arguments, stores the <newvalue> in the <ref>,
+possibly dereferencing the symbolic refs, after verifying that
+the current value of the <ref> matches <oldvalue>.
+E.g. `git-update-ref refs/heads/master <newvalue> <oldvalue>`
+updates the master branch head to <newvalue> only if its current
+value is <oldvalue>.
+
+It also allows a "ref" file to be a symbolic pointer to another
+ref file by starting with the four-byte header sequence of
+"ref:".
+
+More importantly, it allows the update of a ref file to follow
+these symbolic pointers, whether they are symlinks or these
+"regular file symbolic refs". It follows *real* symlinks only
+if they start with "refs/": otherwise it will just try to read
+them and update them as a regular file (i.e. it will allow the
+filesystem to follow them, but will overwrite such a symlink to
+somewhere else with a regular filename).
+
+In general, using
+
+ git-update-ref HEAD "$head"
+
+should be a _lot_ safer than doing
+
+ echo "$head" > "$GIT_DIR/HEAD"
+
+both from a symlink following standpoint *and* an error checking
+standpoint. The "refs/" rule for symlinks means that symlinks
+that point to "outside" the tree are safe: they'll be followed
+for reading but not for writing (so we'll never write through a
+ref symlink to some other tree, if you have copied a whole
+archive by creating a symlink tree).
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-update-server-info.txt b/Documentation/git-update-server-info.txt
new file mode 100644
index 000000000..3d0dea07f
--- /dev/null
+++ b/Documentation/git-update-server-info.txt
@@ -0,0 +1,58 @@
+git-update-server-info(1)
+=========================
+
+NAME
+----
+git-update-server-info - Update auxiliary info file to help dumb servers
+
+
+SYNOPSIS
+--------
+'git-update-server-info' [--force]
+
+DESCRIPTION
+-----------
+A dumb server that does not do on-the-fly pack generations can
+have some auxiliary information files in $GIT_DIR/info and
+$GIT_OBJECT_DIRECTORY/info directories to help clients discover
+what references and packs the server has and make optimized
+pull decisions. This command generates such auxiliary files.
+
+
+OPTIONS
+-------
+
+--force::
+ Update the info files from scratch.
+
+
+OUTPUT
+------
+
+Currently the command updates the following files. Please see
+link:repository-layout.html[repository-layout] for description
+of what they are for:
+
+* objects/info/packs
+
+* info/refs
+
+
+BUGS
+----
+When you remove an existing ref, the command fails to update
+info/refs file unless `--force` flag is given.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
new file mode 100644
index 000000000..3d8f8ef66
--- /dev/null
+++ b/Documentation/git-upload-pack.txt
@@ -0,0 +1,39 @@
+git-upload-pack(1)
+==================
+
+NAME
+----
+git-upload-pack - Send missing objects packed.
+
+
+SYNOPSIS
+--------
+'git-upload-pack' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-clone-pack' and/or 'git-fetch-pack', learns what
+objects the other side is missing, and sends them after packing.
+
+This command is usually not invoked directly by the end user.
+The UI for the protocol is on the 'git-fetch-pack' side, and the
+program pair is meant to be used to pull updates from a remote
+repository. For push operations, see 'git-send-pack'.
+
+
+OPTIONS
+-------
+<directory>::
+ The repository to sync from.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt
new file mode 100644
index 000000000..c22d34f5f
--- /dev/null
+++ b/Documentation/git-var.txt
@@ -0,0 +1,61 @@
+git-var(1)
+==========
+
+NAME
+----
+git-var - Print the git users identity
+
+
+SYNOPSIS
+--------
+git-var [ -l | <variable> ]
+
+DESCRIPTION
+-----------
+Prints a git logical variable.
+
+OPTIONS
+-------
+-l::
+ Cause the logical variables to be listed.
+
+EXAMPLE
+--------
+ $ git-var GIT_AUTHOR_IDENT
+ Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
+
+
+VARIABLES
+----------
+GIT_AUTHOR_IDENT::
+ The author of a piece of code.
+
+GIT_COMMITTER_IDENT::
+ The person who put a piece of code into git.
+
+Diagnostics
+-----------
+You don't exist. Go away!::
+ The passwd(5) gecos field couldn't be read
+Your parents must have hated you!::
+ The password(5) gecos field is longer than a giant static buffer.
+Your sysadmin must hate you!::
+ The password(5) name field is longer than a giant static buffer.
+
+See Also
+--------
+gitlink:git-commit-tree[1]
+gitlink:git-tag[1]
+
+Author
+------
+Written by Eric Biederman <ebiederm@xmission.com>
+
+Documentation
+--------------
+Documentation by Eric Biederman and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt
new file mode 100644
index 000000000..cd74ffd39
--- /dev/null
+++ b/Documentation/git-verify-pack.txt
@@ -0,0 +1,52 @@
+git-verify-pack(1)
+==================
+
+NAME
+----
+git-verify-pack - Validate packed git archive files.
+
+
+SYNOPSIS
+--------
+'git-verify-pack' [-v] <pack>.idx ...
+
+
+DESCRIPTION
+-----------
+Reads given idx file for packed git archive created with
+git-pack-objects command and verifies idx file and the
+corresponding pack file.
+
+OPTIONS
+-------
+<pack>.idx ...::
+ The idx files to verify.
+
+-v::
+ After verifying the pack, show list of objects contained
+ in the pack.
+
+OUTPUT FORMAT
+-------------
+When specifying the -v option the format used is:
+
+ SHA1 type size offset-in-packfile
+
+for objects that are not deltified in the pack, and
+
+ SHA1 type size offset-in-packfile depth base-SHA1
+
+for objects that are deltified.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt
new file mode 100644
index 000000000..b8a73c47a
--- /dev/null
+++ b/Documentation/git-verify-tag.txt
@@ -0,0 +1,32 @@
+git-verify-tag(1)
+=================
+
+NAME
+----
+git-verify-tag - Check the GPG signature of tag.
+
+SYNOPSIS
+--------
+'git-verify-tag' <tag>
+
+DESCRIPTION
+-----------
+Validates the gpg signature created by git-tag.
+
+OPTIONS
+-------
+<tag>::
+ SHA1 identifier of a git tag object.
+
+Author
+------
+Written by Jan Harkes <jaharkes@cs.cmu.edu> and Eric W. Biederman <ebiederm@xmission.com>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt
new file mode 100644
index 000000000..6c150b026
--- /dev/null
+++ b/Documentation/git-whatchanged.txt
@@ -0,0 +1,81 @@
+git-whatchanged(1)
+==================
+
+NAME
+----
+git-whatchanged - Show logs with difference each commit introduces.
+
+
+SYNOPSIS
+--------
+'git-whatchanged' <option>...
+
+DESCRIPTION
+-----------
+Shows commit logs and diff output each commit introduces. The
+command internally invokes 'git-rev-list' piped to
+'git-diff-tree', and takes command line options for both of
+these commands.
+
+This manual page describes only the most frequently used options.
+
+
+OPTIONS
+-------
+-p::
+ Show textual diffs, instead of the git internal diff
+ output format that is useful only to tell the changed
+ paths and their nature of changes.
+
+--max-count=<n>::
+ Limit output to <n> commits.
+
+<since>..<until>::
+ Limit output to between the two named commits (bottom
+ exclusive, top inclusive).
+
+-r::
+ Show git internal diff output, but for the whole tree,
+ not just the top level.
+
+--pretty=<format>::
+ Controls the output format for the commit logs.
+ <format> can be one of 'raw', 'medium', 'short', 'full',
+ and 'oneline'.
+
+-m::
+ By default, differences for merge commits are not shown.
+ With this flag, show differences to that commit from all
+ of its parents.
+
+ However, it is not very useful in general, although it
+ *is* useful on a file-by-file basis.
+
+Examples
+--------
+git-whatchanged -p v2.6.12.. include/scsi drivers/scsi::
+
+ Show as patches the commits since version 'v2.6.12' that changed
+ any file in the include/scsi or drivers/scsi subdirectories
+
+git-whatchanged --since="2 weeks ago" -- gitk::
+
+ Show the changes during the last two weeks to the file 'gitk'.
+ The "--" is necessary to avoid confusion with the *branch* named
+ 'gitk'
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt
new file mode 100644
index 000000000..abee05f6f
--- /dev/null
+++ b/Documentation/git-write-tree.txt
@@ -0,0 +1,42 @@
+git-write-tree(1)
+=================
+
+NAME
+----
+git-write-tree - Creates a tree object from the current index
+
+
+SYNOPSIS
+--------
+'git-write-tree' [--missing-ok]
+
+DESCRIPTION
+-----------
+Creates a tree object using the current index.
+
+The index must be merged.
+
+Conceptually, "git-write-tree" sync()s the current index contents
+into a set of tree files.
+In order to have that match what is actually in your directory right
+now, you need to have done a "git-update-index" phase before you did the
+"git-write-tree".
+
+OPTIONS
+-------
+--missing-ok::
+ Normally "git-write-tree" ensures that the objects referenced by the
+ directory exist in the object database. This option disables this check.
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git.txt b/Documentation/git.txt
new file mode 100644
index 000000000..338e5acb8
--- /dev/null
+++ b/Documentation/git.txt
@@ -0,0 +1,570 @@
+git(7)
+======
+
+NAME
+----
+git - the stupid content tracker
+
+
+SYNOPSIS
+--------
+'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ARGS]
+
+DESCRIPTION
+-----------
+'git' is both a program and a directory content tracker system.
+The program 'git' is just a wrapper to reach the core git programs
+(or a potty if you like, as it's not exactly porcelain but still
+brings your stuff to the plumbing).
+
+OPTIONS
+-------
+--version::
+ prints the git suite version that the 'git' program came from.
+
+--help::
+ prints the synopsis and a list of available commands.
+ If a git command is named this option will bring up the
+ man-page for that command.
+
+--exec-path::
+ path to wherever your core git programs are installed.
+ This can also be controlled by setting the GIT_EXEC_PATH
+ environment variable. If no path is given 'git' will print
+ the current setting and then exit.
+
+CORE GIT COMMANDS
+-----------------
+Before reading this cover to cover, you may want to take a look
+at the link:tutorial.html[tutorial] document.
+
+The <<Discussion>> section below contains much useful definition and
+clarification info - read that first. And of the commands, I suggest
+reading gitlink:git-update-index[1] and
+gitlink:git-read-tree[1] first - I wish I had!
+
+If you are migrating from CVS, link:cvs-migration.html[cvs migration]
+document may be helpful after you finish the tutorial.
+
+After you get the general feel from the tutorial and this
+overview page, you may want to take a look at the
+link:howto-index.html[howto] documents.
+
+
+David Greaves <david@dgreaves.com>
+08/05/05
+
+Updated by Junio C Hamano <junkio@cox.net> on 2005-05-05 to
+reflect recent changes.
+
+Commands Overview
+-----------------
+The git commands can helpfully be split into those that manipulate
+the repository, the index and the working fileset, those that
+interrogate and compare them, and those that moves objects and
+references between repositories.
+
+In addition, git itself comes with a spartan set of porcelain
+commands. They are usable but are not meant to compete with real
+Porcelains.
+
+There are also some ancillary programs that can be viewed as useful
+aids for using the core commands but which are unlikely to be used by
+SCMs layered over git.
+
+Manipulation commands
+~~~~~~~~~~~~~~~~~~~~~
+gitlink:git-apply[1]::
+ Reads a "diff -up1" or git generated patch file and
+ applies it to the working tree.
+
+gitlink:git-checkout-index[1]::
+ Copy files from the index to the working directory
+
+gitlink:git-commit-tree[1]::
+ Creates a new commit object
+
+gitlink:git-hash-object[1]::
+ Computes the object ID from a file.
+
+gitlink:git-index-pack[1]::
+ Build pack index file for an existing packed archive.
+
+gitlink:git-init-db[1]::
+ Creates an empty git object database
+
+gitlink:git-merge-index[1]::
+ Runs a merge for files needing merging
+
+gitlink:git-mktag[1]::
+ Creates a tag object
+
+gitlink:git-pack-objects[1]::
+ Creates a packed archive of objects.
+
+gitlink:git-prune-packed[1]::
+ Remove extra objects that are already in pack files.
+
+gitlink:git-read-tree[1]::
+ Reads tree information into the directory index
+
+gitlink:git-unpack-objects[1]::
+ Unpacks objects out of a packed archive.
+
+gitlink:git-update-index[1]::
+ Modifies the index or directory cache
+
+gitlink:git-write-tree[1]::
+ Creates a tree from the current index
+
+
+Interrogation commands
+~~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-cat-file[1]::
+ Provide content or type information for repository objects
+
+gitlink:git-diff-index[1]::
+ Compares content and mode of blobs between the index and repository
+
+gitlink:git-diff-files[1]::
+ Compares files in the working tree and the index
+
+gitlink:git-diff-stages[1]::
+ Compares two "merge stages" in the index file.
+
+gitlink:git-diff-tree[1]::
+ Compares the content and mode of blobs found via two tree objects
+
+gitlink:git-fsck-objects[1]::
+ Verifies the connectivity and validity of the objects in the database
+
+gitlink:git-ls-files[1]::
+ Information about files in the index/working directory
+
+gitlink:git-ls-tree[1]::
+ Displays a tree object in human readable form
+
+gitlink:git-merge-base[1]::
+ Finds as good a common ancestor as possible for a merge
+
+gitlink:git-name-rev[1]::
+ Find symbolic names for given revs
+
+gitlink:git-rev-list[1]::
+ Lists commit objects in reverse chronological order
+
+gitlink:git-show-index[1]::
+ Displays contents of a pack idx file.
+
+gitlink:git-tar-tree[1]::
+ Creates a tar archive of the files in the named tree
+
+gitlink:git-unpack-file[1]::
+ Creates a temporary file with a blob's contents
+
+gitlink:git-var[1]::
+ Displays a git logical variable
+
+gitlink:git-verify-pack[1]::
+ Validates packed git archive files
+
+The interrogate commands may create files - and you can force them to
+touch the working file set - but in general they don't
+
+
+Synching repositories
+~~~~~~~~~~~~~~~~~~~~~
+
+gitlink:git-clone-pack[1]::
+ Clones a repository into the current repository (engine
+ for ssh and local transport)
+
+gitlink:git-fetch-pack[1]::
+ Updates from a remote repository.
+
+gitlink:git-http-fetch[1]::
+ Downloads a remote git repository via HTTP
+
+gitlink:git-local-fetch[1]::
+ Duplicates another git repository on a local system
+
+gitlink:git-peek-remote[1]::
+ Lists references on a remote repository using upload-pack protocol.
+
+gitlink:git-receive-pack[1]::
+ Invoked by 'git-send-pack' to receive what is pushed to it.
+
+gitlink:git-send-pack[1]::
+ Pushes to a remote repository, intelligently.
+
+gitlink:git-shell[1]::
+ Restricted shell for GIT-only SSH access.
+
+gitlink:git-ssh-fetch[1]::
+ Pulls from a remote repository over ssh connection
+
+gitlink:git-ssh-upload[1]::
+ Helper "server-side" program used by git-ssh-fetch
+
+gitlink:git-update-server-info[1]::
+ Updates auxiliary information on a dumb server to help
+ clients discover references and packs on it.
+
+gitlink:git-upload-pack[1]::
+ Invoked by 'git-clone-pack' and 'git-fetch-pack' to push
+ what are asked for.
+
+
+Porcelain-ish Commands
+----------------------
+
+gitlink:git-add[1]::
+ Add paths to the index file.
+
+gitlink:git-am[1]::
+ Apply patches from a mailbox, but cooler.
+
+gitlink:git-applymbox[1]::
+ Apply patches from a mailbox.
+
+gitlink:git-bisect[1]::
+ Find the change that introduced a bug.
+
+gitlink:git-branch[1]::
+ Create and Show branches.
+
+gitlink:git-checkout[1]::
+ Checkout and switch to a branch.
+
+gitlink:git-cherry-pick[1]::
+ Cherry-pick the effect of an existing commit.
+
+gitlink:git-clone[1]::
+ Clones a repository into a new directory.
+
+gitlink:git-commit[1]::
+ Record changes to the repository.
+
+gitlink:git-diff[1]::
+ Show changes between commits, commit and working tree, etc.
+
+gitlink:git-fetch[1]::
+ Download from a remote repository via various protocols.
+
+gitlink:git-format-patch[1]::
+ Prepare patches for e-mail submission.
+
+gitlink:git-grep[1]::
+ Print lines matching a pattern
+
+gitlink:git-log[1]::
+ Shows commit logs.
+
+gitlink:git-ls-remote[1]::
+ Shows references in a remote or local repository.
+
+gitlink:git-merge[1]::
+ Grand unified merge driver.
+
+gitlink:git-mv[1]::
+ Move or rename a file, a directory, or a symlink.
+
+gitlink:git-octopus[1]::
+ Merge more than two commits.
+
+gitlink:git-pull[1]::
+ Fetch from and merge with a remote repository.
+
+gitlink:git-push[1]::
+ Update remote refs along with associated objects.
+
+gitlink:git-rebase[1]::
+ Rebase local commits to new upstream head.
+
+gitlink:git-repack[1]::
+ Pack unpacked objects in a repository.
+
+gitlink:git-reset[1]::
+ Reset current HEAD to the specified state.
+
+gitlink:git-resolve[1]::
+ Merge two commits.
+
+gitlink:git-revert[1]::
+ Revert an existing commit.
+
+gitlink:git-shortlog[1]::
+ Summarizes 'git log' output.
+
+gitlink:git-show-branch[1]::
+ Show branches and their commits.
+
+gitlink:git-status[1]::
+ Shows the working tree status.
+
+gitlink:git-verify-tag[1]::
+ Check the GPG signature of tag.
+
+gitlink:git-whatchanged[1]::
+ Shows commit logs and differences they introduce.
+
+
+Ancillary Commands
+------------------
+Manipulators:
+
+gitlink:git-applypatch[1]::
+ Apply one patch extracted from an e-mail.
+
+gitlink:git-archimport[1]::
+ Import an arch repository into git.
+
+gitlink:git-convert-objects[1]::
+ Converts old-style git repository
+
+gitlink:git-cvsimport[1]::
+ Salvage your data out of another SCM people love to hate.
+
+gitlink:git-lost-found[1]::
+ Recover lost refs that luckily have not yet been pruned.
+
+gitlink:git-merge-one-file[1]::
+ The standard helper program to use with "git-merge-index"
+
+gitlink:git-prune[1]::
+ Prunes all unreachable objects from the object database
+
+gitlink:git-relink[1]::
+ Hardlink common objects in local repositories.
+
+gitlink:git-svnimport[1]::
+ Import a SVN repository into git.
+
+gitlink:git-sh-setup[1]::
+ Common git shell script setup code.
+
+gitlink:git-symbolic-ref[1]::
+ Read and modify symbolic refs
+
+gitlink:git-tag[1]::
+ An example script to create a tag object signed with GPG
+
+gitlink:git-update-ref[1]::
+ Update the object name stored in a ref safely.
+
+
+Interrogators:
+
+gitlink:git-check-ref-format[1]::
+ Make sure ref name is well formed.
+
+gitlink:git-cherry[1]::
+ Find commits not merged upstream.
+
+gitlink:git-count-objects[1]::
+ Count unpacked number of objects and their disk consumption.
+
+gitlink:git-daemon[1]::
+ A really simple server for git repositories.
+
+gitlink:git-get-tar-commit-id[1]::
+ Extract commit ID from an archive created using git-tar-tree.
+
+gitlink:git-mailinfo[1]::
+ Extracts patch from a single e-mail message.
+
+gitlink:git-mailsplit[1]::
+ git-mailsplit.
+
+gitlink:git-patch-id[1]::
+ Compute unique ID for a patch.
+
+gitlink:git-parse-remote[1]::
+ Routines to help parsing $GIT_DIR/remotes/
+
+gitlink:git-request-pull[1]::
+ git-request-pull.
+
+gitlink:git-rev-parse[1]::
+ Pick out and massage parameters.
+
+gitlink:git-send-email[1]::
+ Send patch e-mails out of "format-patch --mbox" output.
+
+gitlink:git-symbolic-refs[1]::
+ Read and modify symbolic refs.
+
+gitlink:git-stripspace[1]::
+ Filter out empty lines.
+
+
+Commands not yet documented
+---------------------------
+
+gitlink:gitk[1]::
+ gitk.
+
+
+Configuration Mechanism
+-----------------------
+
+Starting from 0.99.9 (actually mid 0.99.8.GIT), .git/config file
+is used to hold per-repository configuration options. It is a
+simple text file modelled after `.ini` format familiar to some
+people. Here is an example:
+
+------------
+#
+# This is the config file, and
+# a '#' or ';' character indicates
+# a comment
+#
+
+; core variables
+[core]
+ ; Don't trust file modes
+ filemode = false
+
+; user identity
+[user]
+ name = "Junio C Hamano"
+ email = "junkio@twinsun.com"
+
+------------
+
+Various commands read from the configuration file and adjust
+their operation accordingly.
+
+
+Identifier Terminology
+----------------------
+<object>::
+ Indicates the sha1 identifier for any type of object
+
+<blob>::
+ Indicates a blob object sha1 identifier
+
+<tree>::
+ Indicates a tree object sha1 identifier
+
+<commit>::
+ Indicates a commit object sha1 identifier
+
+<tree-ish>::
+ Indicates a tree, commit or tag object sha1 identifier. A
+ command that takes a <tree-ish> argument ultimately wants to
+ operate on a <tree> object but automatically dereferences
+ <commit> and <tag> objects that point at a <tree>.
+
+<type>::
+ Indicates that an object type is required.
+ Currently one of: blob/tree/commit/tag
+
+<file>::
+ Indicates a filename - always relative to the root of
+ the tree structure GIT_INDEX_FILE describes.
+
+Symbolic Identifiers
+--------------------
+Any git command accepting any <object> can also use the following
+symbolic notation:
+
+HEAD::
+ indicates the head of the repository (ie the contents of
+ `$GIT_DIR/HEAD`)
+<tag>::
+ a valid tag 'name'+
+ (ie the contents of `$GIT_DIR/refs/tags/<tag>`)
+<head>::
+ a valid head 'name'+
+ (ie the contents of `$GIT_DIR/refs/heads/<head>`)
+<snap>::
+ a valid snapshot 'name'+
+ (ie the contents of `$GIT_DIR/refs/snap/<snap>`)
+
+
+File/Directory Structure
+------------------------
+
+Please see link:repository-layout.html[repository layout] document.
+
+Higher level SCMs may provide and manage additional information in the
+GIT_DIR.
+
+
+Terminology
+-----------
+Please see link:glossary.html[glossary] document.
+
+
+Environment Variables
+---------------------
+Various git commands use the following environment variables:
+
+The git Repository
+~~~~~~~~~~~~~~~~~~
+These environment variables apply to 'all' core git commands. Nb: it
+is worth noting that they may be used/overridden by SCMS sitting above
+git so take care if using Cogito etc
+
+'GIT_INDEX_FILE'::
+ This environment allows the specification of an alternate
+ index file. If not specified, the default of `$GIT_DIR/index`
+ is used.
+
+'GIT_OBJECT_DIRECTORY'::
+ If the object storage directory is specified via this
+ environment variable then the sha1 directories are created
+ underneath - otherwise the default `$GIT_DIR/objects`
+ directory is used.
+
+'GIT_ALTERNATE_OBJECT_DIRECTORIES'::
+ Due to the immutable nature of git objects, old objects can be
+ archived into shared, read-only directories. This variable
+ specifies a ":" separated list of git object directories which
+ can be used to search for git objects. New objects will not be
+ written to these directories.
+
+'GIT_DIR'::
+ If the 'GIT_DIR' environment variable is set then it specifies
+ a path to use instead of `./.git` for the base of the
+ repository.
+
+git Commits
+~~~~~~~~~~~
+'GIT_AUTHOR_NAME'::
+'GIT_AUTHOR_EMAIL'::
+'GIT_AUTHOR_DATE'::
+'GIT_COMMITTER_NAME'::
+'GIT_COMMITTER_EMAIL'::
+ see gitlink:git-commit-tree[1]
+
+git Diffs
+~~~~~~~~~
+'GIT_DIFF_OPTS'::
+'GIT_EXTERNAL_DIFF'::
+ see the "generating patches" section in :
+ gitlink:git-diff-index[1];
+ gitlink:git-diff-files[1];
+ gitlink:git-diff-tree[1]
+
+Discussion[[Discussion]]
+------------------------
+include::../README[]
+
+Authors
+-------
+ git's founding father is Linus Torvalds <torvalds@osdl.org>.
+ The current git nurse is Junio C. Hamano <junkio@cox.net>.
+ The git potty was written by Andres Ericsson <ae@op5.se>.
+ General upbringing is handled by the git-list <git@vger.kernel.org>.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt
new file mode 100644
index 000000000..eb126d7a4
--- /dev/null
+++ b/Documentation/gitk.txt
@@ -0,0 +1,51 @@
+gitk(1)
+=======
+
+NAME
+----
+gitk - Some git command not yet documented.
+
+
+SYNOPSIS
+--------
+'gitk' [ --option ] <args>...
+
+DESCRIPTION
+-----------
+Does something not yet documented.
+
+
+OPTIONS
+-------
+--option::
+ Some option not yet documented.
+
+<args>...::
+ Some argument not yet documented.
+
+
+Examples
+--------
+gitk v2.6.12.. include/scsi drivers/scsi::
+
+ Show as the changes since version 'v2.6.12' that changed any
+ file in the include/scsi or drivers/scsi subdirectories
+
+gitk --since="2 weeks ago" -- gitk::
+
+ Show the changes during the last two weeks to the file 'gitk'.
+ The "--" is necessary to avoid confusion with the *branch* named
+ 'gitk'
+
+Author
+------
+Written by Paul Mackerras <paulus@samba.org>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt
new file mode 100644
index 000000000..07df6b48b
--- /dev/null
+++ b/Documentation/glossary.txt
@@ -0,0 +1,242 @@
+object::
+ The unit of storage in git. It is uniquely identified by
+ the SHA1 of its contents. Consequently, an object can not
+ be changed.
+
+object name::
+ The unique identifier of an object. The hash of the object's contents
+ using the Secure Hash Algorithm 1 and usually represented by the 40
+ character hexadecimal encoding of the hash of the object (possibly
+ followed by a white space).
+
+SHA1::
+ Synonym for object name.
+
+object identifier::
+ Synonym for object name.
+
+hash::
+ In git's context, synonym to object name.
+
+object database::
+ Stores a set of "objects", and an individial object is identified
+ by its object name. The object usually live in $GIT_DIR/objects/.
+
+blob object::
+ Untyped object, e.g. the contents of a file.
+
+tree object::
+ An object containing a list of file names and modes along with refs
+ to the associated blob and/or tree objects. A tree is equivalent
+ to a directory.
+
+tree::
+ Either a working tree, or a tree object together with the
+ dependent blob and tree objects (i.e. a stored representation
+ of a working tree).
+
+DAG::
+ Directed acyclic graph. The commit objects form a directed acyclic
+ graph, because they have parents (directed), and the graph of commit
+ objects is acyclic (there is no chain which begins and ends with the
+ same object).
+
+index::
+ A collection of files with stat information, whose contents are
+ stored as objects. The index is a stored version of your working
+ tree. Truth be told, it can also contain a second, and even a third
+ version of a working tree, which are used when merging.
+
+index entry::
+ The information regarding a particular file, stored in the index.
+ An index entry can be unmerged, if a merge was started, but not
+ yet finished (i.e. if the index contains multiple versions of
+ that file).
+
+unmerged index:
+ An index which contains unmerged index entries.
+
+cache::
+ Obsolete for: index.
+
+working tree::
+ The set of files and directories currently being worked on,
+ i.e. you can work in your working tree without using git at all.
+
+directory::
+ The list you get with "ls" :-)
+
+revision::
+ A particular state of files and directories which was stored in
+ the object database. It is referenced by a commit object.
+
+checkout::
+ The action of updating the working tree to a revision which was
+ stored in the object database.
+
+commit::
+ As a verb: The action of storing the current state of the index in the
+ object database. The result is a revision.
+ As a noun: Short hand for commit object.
+
+commit object::
+ An object which contains the information about a particular
+ revision, such as parents, committer, author, date and the
+ tree object which corresponds to the top directory of the
+ stored revision.
+
+parent::
+ A commit object contains a (possibly empty) list of the logical
+ predecessor(s) in the line of development, i.e. its parents.
+
+changeset::
+ BitKeeper/cvsps speak for "commit". Since git does not store
+ changes, but states, it really does not make sense to use
+ the term "changesets" with git.
+
+clean::
+ A working tree is clean, if it corresponds to the revision
+ referenced by the current head.
+
+dirty::
+ A working tree is said to be dirty if it contains modifications
+ which have not been committed to the current branch.
+
+head::
+ The top of a branch. It contains a ref to the corresponding
+ commit object.
+
+branch::
+ A non-cyclical graph of revisions, i.e. the complete history of
+ a particular revision, which is called the branch head. The
+ branch heads are stored in $GIT_DIR/refs/heads/.
+
+ref::
+ A 40-byte hex representation of a SHA1 pointing to a particular
+ object. These may be stored in $GIT_DIR/refs/.
+
+head ref::
+ A ref pointing to a head. Often, this is abbreviated to "head".
+ Head refs are stored in $GIT_DIR/refs/heads/.
+
+tree-ish::
+ A ref pointing to either a commit object, a tree object, or a
+ tag object pointing to a tag or commit or tree object.
+
+ent::
+ Favorite synonym to "tree-ish" by some total geeks. See
+ http://en.wikipedia.org/wiki/Ent_(Middle-earth) for an in-depth
+ explanation.
+
+tag object::
+ An object containing a ref pointing to another object, which can
+ contain a message just like a commit object. It can also
+ contain a (PGP) signature, in which case it is called a "signed
+ tag object".
+
+tag::
+ A ref pointing to a tag or commit object. In contrast to a head,
+ a tag is not changed by a commit. Tags (not tag objects) are
+ stored in $GIT_DIR/refs/tags/. A git tag has nothing to do with
+ a Lisp tag (which is called object type in git's context).
+ A tag is most typically used to mark a particular point in the
+ commit ancestry chain.
+
+merge::
+ To merge branches means to try to accumulate the changes since a
+ common ancestor and apply them to the first branch. An automatic
+ merge uses heuristics to accomplish that. Evidently, an automatic
+ merge can fail.
+
+octopus::
+ To merge more than two branches. Also denotes an intelligent
+ predator.
+
+resolve::
+ The action of fixing up manually what a failed automatic merge
+ left behind.
+
+rewind::
+ To throw away part of the development, i.e. to assign the head to
+ an earlier revision.
+
+rebase::
+ To clean a branch by starting from the head of the main line of
+ development ("master"), and reapply the (possibly cherry-picked)
+ changes from that branch.
+
+repository::
+ A collection of refs together with an object database containing
+ all objects, which are reachable from the refs, possibly accompanied
+ by meta data from one or more porcelains. A repository can
+ share an object database with other repositories.
+
+git archive::
+ Synonym for repository (for arch people).
+
+file system::
+ Linus Torvalds originally designed git to be a user space file
+ system, i.e. the infrastructure to hold files and directories.
+ That ensured the efficiency and speed of git.
+
+alternate object database::
+ Via the alternates mechanism, a repository can inherit part of its
+ object database from another object database, which is called
+ "alternate".
+
+reachable::
+ An object is reachable from a ref/commit/tree/tag, if there is a
+ chain leading from the latter to the former.
+
+chain::
+ A list of objects, where each object in the list contains a
+ reference to its successor (for example, the successor of a commit
+ could be one of its parents).
+
+fetch::
+ Fetching a branch means to get the branch's head ref from a
+ remote repository, to find out which objects are missing from
+ the local object database, and to get them, too.
+
+pull::
+ Pulling a branch means to fetch it and merge it.
+
+push::
+ Pushing a branch means to get the branch's head ref from a remote
+ repository, find out if it is an ancestor to the branch's local
+ head ref is a direct, and in that case, putting all objects, which
+ are reachable from the local head ref, and which are missing from
+ the remote repository, into the remote object database, and updating
+ the remote head ref. If the remote head is not an ancestor to the
+ local head, the push fails.
+
+pack::
+ A set of objects which have been compressed into one file (to save
+ space or to transmit them efficiently).
+
+pack index::
+ The list of identifiers, and other information, of the objects in a
+ pack, to assist in efficiently accessing the contents of a pack.
+
+core git::
+ Fundamental data structures and utilities of git. Exposes only
+ limited source code management tools.
+
+plumbing::
+ Cute name for core git.
+
+porcelain::
+ Cute name for programs and program suites depending on core git,
+ presenting a high level access to core git. Porcelains expose
+ more of a SCM interface than the plumbing.
+
+object type:
+ One of the identifiers "commit","tree","tag" and "blob" describing
+ the type of an object.
+
+SCM::
+ Source code management (tool).
+
+dircache::
+ You are *waaaaay* behind.
+
diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt
new file mode 100644
index 000000000..7ee3571bc
--- /dev/null
+++ b/Documentation/hooks.txt
@@ -0,0 +1,127 @@
+Hooks used by git
+=================
+
+Hooks are little scripts you can place in `$GIT_DIR/hooks`
+directory to trigger action at certain points. When
+`git-init-db` is run, a handful example hooks are copied in the
+`hooks` directory of the new repository, but by default they are
+all disabled. To enable a hook, make it executable with `chmod
++x`.
+
+This document describes the currently defined hooks.
+
+applypatch-msg
+--------------
+
+This hook is invoked by `git-applypatch` script, which is
+typically invoked by `git-applymbox`. It takes a single
+parameter, the name of the file that holds the proposed commit
+log message. Exiting with non-zero status causes the
+'git-applypatch' to abort before applying the patch.
+
+The hook is allowed to edit the message file in place, and can
+be used to normalize the message into some project standard
+format (if the project has one). It can also be used to refuse
+the commit after inspecting the message file.
+
+The default applypatch-msg hook, when enabled, runs the
+commit-msg hook, if the latter is enabled.
+
+pre-applypatch
+--------------
+
+This hook is invoked by `git-applypatch` script, which is
+typically invoked by `git-applymbox`. It takes no parameter,
+and is invoked after the patch is applied, but before a commit
+is made. Exiting with non-zero status causes the working tree
+after application of the patch not committed.
+
+It can be used to inspect the current working tree and refuse to
+make a commit if it does not pass certain test.
+
+The default pre-applypatch hook, when enabled, runs the
+pre-commit hook, if the latter is enabled.
+
+post-applypatch
+---------------
+
+This hook is invoked by `git-applypatch` script, which is
+typically invoked by `git-applymbox`. It takes no parameter,
+and is invoked after the patch is applied and a commit is made.
+
+This hook is meant primarily for notification, and cannot affect
+the outcome of `git-applypatch`.
+
+pre-commit
+----------
+
+This hook is invoked by `git-commit`, and can be bypassed
+with `\--no-verify` option. It takes no parameter, and is
+invoked before obtaining the proposed commit log message and
+making a commit. Exiting with non-zero status from this script
+causes the `git-commit` to abort.
+
+The default pre-commit hook, when enabled, catches introduction
+of lines with trailing whitespaces and aborts the commit when
+a such line is found.
+
+commit-msg
+----------
+
+This hook is invoked by `git-commit`, and can be bypassed
+with `\--no-verify` option. It takes a single parameter, the
+name of the file that holds the proposed commit log message.
+Exiting with non-zero status causes the `git-commit` to
+abort.
+
+The hook is allowed to edit the message file in place, and can
+be used to normalize the message into some project standard
+format (if the project has one). It can also be used to refuse
+the commit after inspecting the message file.
+
+The default commit-msg hook, when enabled, detects duplicate
+Signed-off-by: lines, and aborts the commit when one is found.
+
+post-commit
+-----------
+
+This hook is invoked by `git-commit`. It takes no
+parameter, and is invoked after a commit is made.
+
+This hook is meant primarily for notification, and cannot affect
+the outcome of `git-commit`.
+
+The default post-commit hook, when enabled, demonstrates how to
+send out a commit notification e-mail.
+
+update
+------
+
+This hook is invoked by `git-receive-pack`, which is invoked
+when a `git push` is done against the repository. It takes
+three parameters, name of the ref being updated, old object name
+stored in the ref, and the new objectname to be stored in the
+ref. Exiting with non-zero status from this hook prevents
+`git-receive-pack` from updating the ref.
+
+This can be used to prevent 'forced' update on certain refs by
+making sure that the object name is a commit object that is a
+descendant of the commit object named by the old object name.
+Another use suggested on the mailing list is to use this hook to
+implement access control which is finer grained than the one
+based on filesystem group.
+
+post-update
+-----------
+
+This hook is invoked by `git-receive-pack`, which is invoked
+when a `git push` is done against the repository. It takes
+variable number of parameters; each of which is the name of ref
+that was actually updated.
+
+This hook is meant primarily for notification, and cannot affect
+the outcome of `git-receive-pack`.
+
+The default post-update hook, when enabled, runs
+`git-update-server-info` to keep the information used by dumb
+transport up-to-date.
diff --git a/Documentation/howto-index.sh b/Documentation/howto-index.sh
new file mode 100755
index 000000000..34aa30c5b
--- /dev/null
+++ b/Documentation/howto-index.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+cat <<\EOF
+GIT Howto Index
+===============
+
+Here is a collection of mailing list postings made by various
+people describing how they use git in their workflow.
+
+EOF
+
+for txt
+do
+ title=`expr "$txt" : '.*/\(.*\)\.txt$'`
+ from=`sed -ne '
+ /^$/q
+ /^From:[ ]/{
+ s///
+ s/^[ ]*//
+ s/[ ]*$//
+ s/^/by /
+ p
+ }
+ ' "$txt"`
+
+ abstract=`sed -ne '
+ /^Abstract:[ ]/{
+ s/^[^ ]*//
+ x
+ s/.*//
+ x
+ : again
+ /^[ ]/{
+ s/^[ ]*//
+ H
+ n
+ b again
+ }
+ x
+ p
+ q
+ }' "$txt"`
+
+ if grep 'Content-type: text/asciidoc' >/dev/null $txt
+ then
+ file=`expr "$txt" : '\(.*\)\.txt$'`.html
+ else
+ file="$txt"
+ fi
+
+ echo "* link:$file[$title] $from
+$abstract
+
+"
+
+done
diff --git a/Documentation/howto/isolate-bugs-with-bisect.txt b/Documentation/howto/isolate-bugs-with-bisect.txt
new file mode 100644
index 000000000..400949564
--- /dev/null
+++ b/Documentation/howto/isolate-bugs-with-bisect.txt
@@ -0,0 +1,65 @@
+From: Linus Torvalds <torvalds () osdl ! org>
+To: git@vger.kernel.org
+Date: 2005-11-08 1:31:34
+Subject: Real-life kernel debugging scenario
+Abstract: Short-n-sweet, Linus tells us how to leverage `git-bisect` to perform
+ bug isolation on a repository where "good" and "bad" revisions are known
+ in order to identify a suspect commit.
+
+
+How To Use git-bisect To Isolate a Bogus Commit
+===============================================
+
+The way to use "git bisect" couldn't be easier.
+
+Figure out what the oldest bad state you know about is (that's usually the
+head of "master", since that's what you just tried to boot and failed at).
+Also, figure out the most recent known-good commit (usually the _previous_
+kernel you ran: and if you've only done a single "pull" in between, it
+will be ORIG_HEAD).
+
+Then do
+
+ git bisect start
+ git bisect bad master <- mark "master" as the bad state
+ git bisect good ORIG_HEAD <- mark ORIG_HEAD as good (or
+ whatever other known-good
+ thing you booted laste)
+
+and at this point "git bisect" will churn for a while, and tell you what
+the mid-point between those two commits are, and check that state out as
+the head of the bew "bisect" branch.
+
+Compile and reboot.
+
+If it's good, just do
+
+ git bisect good <- mark current head as good
+
+otherwise, reboot into a good kernel instead, and do (surprise surprise,
+git really is very intuitive):
+
+ git bisect bad <- mark current head as bad
+
+and whatever you do, git will select a new half-way point. Do this for a
+while, until git tells you exactly which commit was the first bad commit.
+That's your culprit.
+
+It really works wonderfully well, except for the case where there was
+_another_ commit that broke something in between, like introduced some
+stupid compile error. In that case you should not mark that commit good or
+bad: you should try to find another commit close-by, and do a "git reset
+--hard <newcommit>" to try out _that_ commit instead, and then test that
+instead (and mark it good or bad).
+
+You can do "git bisect visualize" while you do all this to see what's
+going on by starting up gitk on the bisection range.
+
+Finally, once you've figured out exactly which commit was bad, you can
+then go back to the master branch, and try reverting just that commit:
+
+ git checkout master
+ git revert <bad-commit-id>
+
+to verify that the top-of-kernel works with that single commit reverted.
+
diff --git a/Documentation/howto/make-dist.txt b/Documentation/howto/make-dist.txt
new file mode 100644
index 000000000..00e330b29
--- /dev/null
+++ b/Documentation/howto/make-dist.txt
@@ -0,0 +1,52 @@
+Date: Fri, 12 Aug 2005 22:39:48 -0700 (PDT)
+From: Linus Torvalds <torvalds@osdl.org>
+To: Dave Jones <davej@redhat.com>
+cc: git@vger.kernel.org
+Subject: Re: Fwd: Re: git checkout -f branch doesn't remove extra files
+Abstract: In this article, Linus talks about building a tarball,
+ incremental patch, and ChangeLog, given a base release and two
+ rc releases, following the convention of giving the patch from
+ the base release and the latest rc, with ChangeLog between the
+ last rc and the latest rc.
+
+On Sat, 13 Aug 2005, Dave Jones wrote:
+>
+> > Git actually has a _lot_ of nifty tools. I didn't realize that people
+> > didn't know about such basic stuff as "git-tar-tree" and "git-ls-files".
+>
+> Maybe its because things are moving so fast :) Or maybe I just wasn't
+> paying attention on that day. (I even read the git changes via RSS,
+> so I should have no excuse).
+
+Well, git-tar-tree has been there since late April - it's actually one of
+those really early commands. I'm pretty sure the RSS feed came later ;)
+
+I use it all the time in doing releases, it's a lot faster than creating a
+tar tree by reading the filesystem (even if you don't have to check things
+out). A hidden pearl.
+
+This is my crappy "release-script":
+
+ [torvalds@g5 ~]$ cat bin/release-script
+ #!/bin/sh
+ stable="$1"
+ last="$2"
+ new="$3"
+ echo "# git-tag v$new"
+ echo "git-tar-tree v$new linux-$new | gzip -9 > ../linux-$new.tar.gz"
+ echo "git-diff-tree -p v$stable v$new | gzip -9 > ../patch-$new.gz"
+ echo "git-rev-list --pretty v$new ^v$last > ../ChangeLog-$new"
+ echo "git-rev-list --pretty=short v$new ^v$last | git-shortlog > ../ShortLog"
+ echo "git-diff-tree -p v$last v$new | git-apply --stat > ../diffstat-$new"
+
+and when I want to do a new kernel release I literally first tag it, and
+then do
+
+ release-script 2.6.12 2.6.13-rc6 2.6.13-rc7
+
+and check that things look sane, and then just cut-and-paste the commands.
+
+Yeah, it's stupid.
+
+ Linus
+
diff --git a/Documentation/howto/rebase-and-edit.txt b/Documentation/howto/rebase-and-edit.txt
new file mode 100644
index 000000000..646c55cc6
--- /dev/null
+++ b/Documentation/howto/rebase-and-edit.txt
@@ -0,0 +1,81 @@
+Date: Sat, 13 Aug 2005 22:16:02 -0700 (PDT)
+From: Linus Torvalds <torvalds@osdl.org>
+To: Steve French <smfrench@austin.rr.com>
+cc: git@vger.kernel.org
+Subject: Re: sending changesets from the middle of a git tree
+Abstract: In this article, Linus demonstrates how a broken commit
+ in a sequence of commits can be removed by rewinding the head and
+ reapplying selected changes.
+
+On Sat, 13 Aug 2005, Linus Torvalds wrote:
+
+> That's correct. Same things apply: you can move a patch over, and create a
+> new one with a modified comment, but basically the _old_ commit will be
+> immutable.
+
+Let me clarify.
+
+You can entirely _drop_ old branches, so commits may be immutable, but
+nothing forces you to keep them. Of course, when you drop a commit, you'll
+always end up dropping all the commits that depended on it, and if you
+actually got somebody else to pull that commit you can't drop it from
+_their_ repository, but undoing things is not impossible.
+
+For example, let's say that you've made a mess of things: you've committed
+three commits "old->a->b->c", and you notice that "a" was broken, but you
+want to save "b" and "c". What you can do is
+
+ # Create a branch "broken" that is the current code
+ # for reference
+ git branch broken
+
+ # Reset the main branch to three parents back: this
+ # effectively undoes the three top commits
+ git reset HEAD^^^
+ git checkout -f
+
+ # Check the result visually to make sure you know what's
+ # going on
+ gitk --all
+
+ # Re-apply the two top ones from "broken"
+ #
+ # First "parent of broken" (aka b):
+ git-diff-tree -p broken^ | git-apply --index
+ git commit --reedit=broken^
+
+ # Then "top of broken" (aka c):
+ git-diff-tree -p broken | git-apply --index
+ git commit --reedit=broken
+
+and you've now re-applied (and possibly edited the comments) the two
+commits b/c, and commit "a" is basically gone (it still exists in the
+"broken" branch, of course).
+
+Finally, check out the end result again:
+
+ # Look at the new commit history
+ gitk --all
+
+to see that everything looks sensible.
+
+And then, you can just remove the broken branch if you decide you really
+don't want it:
+
+ # remove 'broken' branch
+ git branch -d broken
+
+ # Prune old objects if you're really really sure
+ git prune
+
+And yeah, I'm sure there are other ways of doing this. And as usual, the
+above is totally untested, and I just wrote it down in this email, so if
+I've done something wrong, you'll have to figure it out on your own ;)
+
+ Linus
+-
+To unsubscribe from this list: send the line "unsubscribe git" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at http://vger.kernel.org/majordomo-info.html
+
+
diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt
new file mode 100644
index 000000000..b2c021d91
--- /dev/null
+++ b/Documentation/howto/rebase-from-internal-branch.txt
@@ -0,0 +1,168 @@
+From: Junio C Hamano <junkio@cox.net>
+To: git@vger.kernel.org
+Cc: Petr Baudis <pasky@suse.cz>, Linus Torvalds <torvalds@osdl.org>
+Subject: Re: sending changesets from the middle of a git tree
+Date: Sun, 14 Aug 2005 18:37:39 -0700
+Abstract: In this article, JC talks about how he rebases the
+ public "pu" branch using the core GIT tools when he updates
+ the "master" branch, and how "rebase" works. Also discussed
+ is how this applies to individual developers who sends patches
+ upstream.
+
+Petr Baudis <pasky@suse.cz> writes:
+
+> Dear diary, on Sun, Aug 14, 2005 at 09:57:13AM CEST, I got a letter
+> where Junio C Hamano <junkio@cox.net> told me that...
+>> Linus Torvalds <torvalds@osdl.org> writes:
+>>
+>> > Junio, maybe you want to talk about how you move patches from your "pu"
+>> > branch to the real branches.
+>>
+> Actually, wouldn't this be also precisely for what StGIT is intended to?
+
+Exactly my feeling. I was sort of waiting for Catalin to speak
+up. With its basing philosophical ancestry on quilt, this is
+the kind of task StGIT is designed to do.
+
+I just have done a simpler one, this time using only the core
+GIT tools.
+
+I had a handful commits that were ahead of master in pu, and I
+wanted to add some documentation bypassing my usual habit of
+placing new things in pu first. At the beginning, the commit
+ancestry graph looked like this:
+
+ *"pu" head
+ master --> #1 --> #2 --> #3
+
+So I started from master, made a bunch of edits, and committed:
+
+ $ git checkout master
+ $ cd Documentation; ed git.txt ...
+ $ cd ..; git add Documentation/*.txt
+ $ git commit -s -v
+
+NOTE. The -v flag to commit is a handy way to make sure that
+your additions are not introducing bogusly formatted lines.
+
+After the commit, the ancestry graph would look like this:
+
+ *"pu" head
+ master^ --> #1 --> #2 --> #3
+ \
+ \---> master
+
+The old master is now master^ (the first parent of the master).
+The new master commit holds my documentation updates.
+
+Now I have to deal with "pu" branch.
+
+This is the kind of situation I used to have all the time when
+Linus was the maintainer and I was a contributor, when you look
+at "master" branch being the "maintainer" branch, and "pu"
+branch being the "contributor" branch. Your work started at the
+tip of the "maintainer" branch some time ago, you made a lot of
+progress in the meantime, and now the maintainer branch has some
+other commits you do not have yet. And "git rebase" was written
+with the explicit purpose of helping to maintain branches like
+"pu". You _could_ merge master to pu and keep going, but if you
+eventually want to cherrypick and merge some but not necessarily
+all changes back to the master branch, it often makes later
+operations for _you_ easier if you rebase (i.e. carry forward
+your changes) "pu" rather than merge. So I ran "git rebase":
+
+ $ git checkout pu
+ $ git rebase master pu
+
+What this does is to pick all the commits since the current
+branch (note that I now am on "pu" branch) forked from the
+master branch, and forward port these changes.
+
+ master^ --> #1 --> #2 --> #3
+ \ *"pu" head
+ \---> master --> #1' --> #2' --> #3'
+
+The diff between master^ and #1 is applied to master and
+committed to create #1' commit with the commit information (log,
+author and date) taken from commit #1. On top of that #2' and #3'
+commits are made similarly out of #2 and #3 commits.
+
+Old #3 is not recorded in any of the .git/refs/heads/ file
+anymore, so after doing this you will have dangling commit if
+you ran fsck-cache, which is normal. After testing "pu", you
+can run "git prune" to get rid of those original three commits.
+
+While I am talking about "git rebase", I should talk about how
+to do cherrypicking using only the core GIT tools.
+
+Let's go back to the earlier picture, with different labels.
+
+You, as an individual developer, cloned upstream repository and
+amde a couple of commits on top of it.
+
+ *your "master" head
+ upstream --> #1 --> #2 --> #3
+
+You would want changes #2 and #3 incorporated in the upstream,
+while you feel that #1 may need further improvements. So you
+prepare #2 and #3 for e-mail submission.
+
+ $ git format-patch master^^ master
+
+This creates two files, 0001-XXXX.txt and 0002-XXXX.txt. Send
+them out "To: " your project maintainer and "Cc: " your mailing
+list. You could use contributed script git-send-email if
+your host has necessary perl modules for this, but your usual
+MUA would do as long as it does not corrupt whitespaces in the
+patch.
+
+Then you would wait, and you find out that the upstream picked
+up your changes, along with other changes.
+
+ where *your "master" head
+ upstream --> #1 --> #2 --> #3
+ used \
+ to be \--> #A --> #2' --> #3' --> #B --> #C
+ *upstream head
+
+The two commits #2' and #3' in the above picture record the same
+changes your e-mail submission for #2 and #3 contained, but
+probably with the new sign-off line added by the upsteam
+maintainer and definitely with different committer and ancestry
+information, they are different objects from #2 and #3 commits.
+
+You fetch from upstream, but not merge.
+
+ $ git fetch upstream
+
+This leaves the updated upstream head in .git/FETCH_HEAD but
+does not touch your .git/HEAD nor .git/refs/heads/master.
+You run "git rebase" now.
+
+ $ git rebase FETCH_HEAD master
+
+Earlier, I said that rebase applies all the commits from your
+branch on top of the upstream head. Well, I lied. "git rebase"
+is a bit smarter than that and notices that #2 and #3 need not
+be applied, so it only applies #1. The commit ancestry graph
+becomes something like this:
+
+ where *your old "master" head
+ upstream --> #1 --> #2 --> #3
+ used \ your new "master" head*
+ to be \--> #A --> #2' --> #3' --> #B --> #C --> #1'
+ *upstream
+ head
+
+Again, "git prune" would discard the disused commits #1-#3 and
+you continue on starting from the new "master" head, which is
+the #1' commit.
+
+-jc
+
+-
+To unsubscribe from this list: send the line "unsubscribe git" in
+the body of a message to majordomo@vger.kernel.org
+More majordomo info at http://vger.kernel.org/majordomo-info.html
+
+
diff --git a/Documentation/howto/rebuild-from-update-hook.txt b/Documentation/howto/rebuild-from-update-hook.txt
new file mode 100644
index 000000000..ebd025db8
--- /dev/null
+++ b/Documentation/howto/rebuild-from-update-hook.txt
@@ -0,0 +1,83 @@
+Subject: [HOWTO] Using post-update hook
+Message-ID: <7vy86o6usx.fsf@assigned-by-dhcp.cox.net>
+From: Junio C Hamano <junkio@cox.net>
+Date: Fri, 26 Aug 2005 18:19:10 -0700
+Abstract: In this how-to article, JC talks about how he
+ uses the post-update hook to automate git documentation page
+ shown at http://www.kernel.org/pub/software/scm/git/docs/.
+
+The pages under http://www.kernel.org/pub/software/scm/git/docs/
+are built from Documentation/ directory of the git.git project
+and needed to be kept up-to-date. The www.kernel.org/ servers
+are mirrored and I was told that the origin of the mirror is on
+the machine master.kernel.org, on which I was given an account
+when I took over git maintainership from Linus.
+
+The directories relevant to this how-to are these two:
+
+ /pub/scm/git/git.git/ The public git repository.
+ /pub/software/scm/git/docs/ The HTML documentation page.
+
+So I made a repository to generate the documentation under my
+home directory over there.
+
+ $ cd
+ $ mkdir doc-git && cd doc-git
+ $ git clone /pub/scm/git/git.git/ docgen
+
+What needs to happen is to update the $HOME/doc-git/docgen/
+working tree, build HTML docs there and install the result in
+/pub/software/scm/git/docs/ directory. So I wrote a little
+script:
+
+ $ cat >dododoc.sh <<\EOF
+ #!/bin/sh
+ cd $HOME/doc-git/docgen || exit
+
+ unset GIT_DIR
+
+ git pull /pub/scm/git/git.git/ master &&
+ cd Documentation &&
+ make install-webdoc
+ EOF
+
+Initially I used to run this by hand whenever I push into the
+public git repository. Then I did a cron job that ran twice a
+day. The current round uses the post-update hook mechanism,
+like this:
+
+ $ cat >/pub/scm/git/git.git/hooks/post-update <<\EOF
+ #!/bin/sh
+ #
+ # An example hook script to prepare a packed repository for use over
+ # dumb transports.
+ #
+ # To enable this hook, make this file executable by "chmod +x post-update".
+
+ case " $* " in
+ *' refs/heads/master '*)
+ echo $HOME/doc-git/dododoc.sh | at now
+ ;;
+ esac
+ exec git-update-server-info
+ EOF
+ $ chmod +x /pub/scm/git/git.git/hooks/post-update
+
+There are three things worth mentioning:
+
+ - The update-hook is run after the repository accepts a "git
+ push", under my user privilege. It is given the full names
+ of refs that have been updated as arguments. My post-update
+ runs the dododoc.sh script only when the master head is
+ updated.
+
+ - When update-hook is run, GIT_DIR is set to '.' by the calling
+ receive-pack. This is inherited by the dododoc.sh run via
+ the "at" command, and needs to be unset; otherwise, "git
+ pull" it does into $HOME/doc-git/docgen/ repository would not
+ work correctly.
+
+ - This is still crude and does not protect against simultaneous
+ make invocations stomping on each other. I would need to add
+ some locking mechanism for this.
+
diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt
new file mode 100644
index 000000000..5a7e0cfe0
--- /dev/null
+++ b/Documentation/howto/revert-branch-rebase.txt
@@ -0,0 +1,200 @@
+From: Junio C Hamano <junkio@cox.net>
+To: git@vger.kernel.org
+Subject: [HOWTO] Reverting an existing commit
+Abstract: In this article, JC gives a small real-life example of using
+ 'git revert' command, and using a temporary branch and tag for safety
+ and easier sanity checking.
+Date: Mon, 29 Aug 2005 21:39:02 -0700
+Content-type: text/asciidoc
+Message-ID: <7voe7g3uop.fsf@assigned-by-dhcp.cox.net>
+
+Reverting an existing commit
+============================
+
+One of the changes I pulled into the 'master' branch turns out to
+break building GIT with GCC 2.95. While they were well intentioned
+portability fixes, keeping things working with gcc-2.95 was also
+important. Here is what I did to revert the change in the 'master'
+branch and to adjust the 'pu' branch, using core GIT tools and
+barebone Porcelain.
+
+First, prepare a throw-away branch in case I screw things up.
+
+------------------------------------------------
+$ git checkout -b revert-c99 master
+------------------------------------------------
+
+Now I am on the 'revert-c99' branch. Let's figure out which commit to
+revert. I happen to know that the top of the 'master' branch is a
+merge, and its second parent (i.e. foreign commit I merged from) has
+the change I would want to undo. Further I happen to know that that
+merge introduced 5 commits or so:
+
+------------------------------------------------
+$ git show-branch --more=4 master master^2 | head
+! [master] Merge refs/heads/portable from http://www.cs.berkeley....
+ ! [master^2] Replace C99 array initializers with code.
+--
++ [master] Merge refs/heads/portable from http://www.cs.berkeley....
+++ [master^2] Replace C99 array initializers with code.
+++ [master^2~1] Replace unsetenv() and setenv() with older putenv().
+++ [master^2~2] Include sys/time.h in daemon.c.
+++ [master^2~3] Fix ?: statements.
+++ [master^2~4] Replace zero-length array decls with [].
++ [master~1] tutorial note about git branch
+------------------------------------------------
+
+The '--more=4' above means "after we reach the merge base of refs,
+show until we display four more common commits". That last commit
+would have been where the "portable" branch was forked from the main
+git.git repository, so this would show everything on both branches
+since then. I just limited the output to the first handful using
+'head'.
+
+Now I know 'master^2~4' (pronounce it as "find the second parent of
+the 'master', and then go four generations back following the first
+parent") is the one I would want to revert. Since I also want to say
+why I am reverting it, the '-n' flag is given to 'git revert'. This
+prevents it from actually making a commit, and instead 'git revert'
+leaves the commit log message it wanted to use in '.msg' file:
+
+------------------------------------------------
+$ git revert -n master^2~4
+$ cat .msg
+Revert "Replace zero-length array decls with []."
+
+This reverts 6c5f9baa3bc0d63e141e0afc23110205379905a4 commit.
+$ git diff HEAD ;# to make sure what we are reverting makes sense.
+$ make CC=gcc-2.95 clean test ;# make sure it fixed the breakage.
+$ make clean test ;# make sure it did not cause other breakage.
+------------------------------------------------
+
+The reverted change makes sense (from reading the 'diff' output), does
+fix the problem (from 'make CC=gcc-2.95' test), and does not cause new
+breakage (from the last 'make test'). I'm ready to commit:
+
+------------------------------------------------
+$ git commit -a -s ;# read .msg into the log,
+ # and explain why I am reverting.
+------------------------------------------------
+
+I could have screwed up in any of the above steps, but in the worst
+case I could just have done 'git checkout master' to start over.
+Fortunately I did not have to; what I have in the current branch
+'revert-c99' is what I want. So merge that back into 'master':
+
+------------------------------------------------
+$ git checkout master
+$ git resolve master revert-c99 fast ;# this should be a fast forward
+Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
+ cache.h | 8 ++++----
+ commit.c | 2 +-
+ ls-files.c | 2 +-
+ receive-pack.c | 2 +-
+ server-info.c | 2 +-
+ 5 files changed, 8 insertions(+), 8 deletions(-)
+------------------------------------------------
+
+The 'fast' in the above 'git resolve' is not a magic. I knew this
+'resolve' would result in a fast forward merge, and if not, there is
+something very wrong (so I would do 'git reset' on the 'master' branch
+and examine the situation). When a fast forward merge is done, the
+message parameter to 'git resolve' is discarded, because no new commit
+is created. You could have said 'junk' or 'nothing' there as well.
+
+There is no need to redo the test at this point. We fast forwarded
+and we know 'master' matches 'revert-c99' exactly. In fact:
+
+------------------------------------------------
+$ git diff master..revert-c99
+------------------------------------------------
+
+says nothing.
+
+Then we rebase the 'pu' branch as usual.
+
+------------------------------------------------
+$ git checkout pu
+$ git tag pu-anchor pu
+$ git rebase master
+* Applying: Redo "revert" using three-way merge machinery.
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+* Applying: Remove git-apply-patch-script.
+First trying simple merge strategy to cherry-pick.
+Simple cherry-pick fails; trying Automatic cherry-pick.
+Removing Documentation/git-apply-patch-script.txt
+Removing git-apply-patch-script
+Finished one cherry-pick.
+* Applying: Document "git cherry-pick" and "git revert"
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+* Applying: mailinfo and applymbox updates
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+* Applying: Show commits in topo order and name all commits.
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+* Applying: More documentation updates.
+First trying simple merge strategy to cherry-pick.
+Finished one cherry-pick.
+------------------------------------------------
+
+The temporary tag 'pu-anchor' is me just being careful, in case 'git
+rebase' screws up. After this, I can do these for sanity check:
+
+------------------------------------------------
+$ git diff pu-anchor..pu ;# make sure we got the master fix.
+$ make CC=gcc-2.95 clean test ;# make sure it fixed the breakage.
+$ make clean test ;# make sure it did not cause other breakage.
+------------------------------------------------
+
+Everything is in the good order. I do not need the temporary branch
+nor tag anymore, so remove them:
+
+------------------------------------------------
+$ rm -f .git/refs/tags/pu-anchor
+$ git branch -d revert-c99
+------------------------------------------------
+
+It was an emergency fix, so we might as well merge it into the
+'release candidate' branch, although I expect the next release would
+be some days off:
+
+------------------------------------------------
+$ git checkout rc
+$ git pull . master
+Packing 0 objects
+Unpacking 0 objects
+
+* committish: e3a693c... refs/heads/master from .
+Trying to merge e3a693c... into 8c1f5f0... using 10d781b...
+Committed merge 7fb9b7262a1d1e0a47bbfdcbbcf50ce0635d3f8f
+ cache.h | 8 ++++----
+ commit.c | 2 +-
+ ls-files.c | 2 +-
+ receive-pack.c | 2 +-
+ server-info.c | 2 +-
+ 5 files changed, 8 insertions(+), 8 deletions(-)
+------------------------------------------------
+
+And the final repository status looks like this:
+
+------------------------------------------------
+$ git show-branch --more=1 master pu rc
+! [master] Revert "Replace zero-length array decls with []."
+ ! [pu] git-repack: Add option to repack all objects.
+ * [rc] Merge refs/heads/master from .
+---
+ + [pu] git-repack: Add option to repack all objects.
+ + [pu~1] More documentation updates.
+ + [pu~2] Show commits in topo order and name all commits.
+ + [pu~3] mailinfo and applymbox updates
+ + [pu~4] Document "git cherry-pick" and "git revert"
+ + [pu~5] Remove git-apply-patch-script.
+ + [pu~6] Redo "revert" using three-way merge machinery.
+ + [rc] Merge refs/heads/master from .
++++ [master] Revert "Replace zero-length array decls with []."
+ + [rc~1] Merge refs/heads/master from .
++++ [master~1] Merge refs/heads/portable from http://www.cs.berkeley....
+------------------------------------------------
diff --git a/Documentation/howto/using-topic-branches.txt b/Documentation/howto/using-topic-branches.txt
new file mode 100644
index 000000000..4698abe46
--- /dev/null
+++ b/Documentation/howto/using-topic-branches.txt
@@ -0,0 +1,288 @@
+Date: Mon, 15 Aug 2005 12:17:41 -0700
+From: tony.luck@intel.com
+Subject: Some tutorial text (was git/cogito workshop/bof at linuxconf au?)
+Abstract: In this article, Tony Luck discusses how he uses GIT
+ as a Linux subsystem maintainer.
+
+Here's something that I've been putting together on how I'm using
+GIT as a Linux subsystem maintainer.
+
+-Tony
+
+Last updated w.r.t. GIT 0.99.9f
+
+Linux subsystem maintenance using GIT
+-------------------------------------
+
+My requirements here are to be able to create two public trees:
+
+1) A "test" tree into which patches are initially placed so that they
+can get some exposure when integrated with other ongoing development.
+This tree is available to Andrew for pulling into -mm whenever he wants.
+
+2) A "release" tree into which tested patches are moved for final
+sanity checking, and as a vehicle to send them upstream to Linus
+(by sending him a "please pull" request.)
+
+Note that the period of time that each patch spends in the "test" tree
+is dependent on the complexity of the change. Since GIT does not support
+cherry picking, it is not practical to simply apply all patches to the
+test tree and then pull to the release tree as that would leave trivial
+patches blocked in the test tree waiting for complex changes to accumulate
+enough test time to graduate.
+
+Back in the BitKeeper days I achieved this my creating small forests of
+temporary trees, one tree for each logical grouping of patches, and then
+pulling changes from these trees first to the test tree, and then to the
+release tree. At first I replicated this in GIT, but then I realised
+that I could so this far more efficiently using branches inside a single
+GIT repository.
+
+So here is the step-by-step guide how this all works for me.
+
+First create your work tree by cloning Linus's public tree:
+
+ $ git clone rsync://rsync.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git work
+
+Change directory into the cloned tree you just created
+
+ $ cd work
+
+Set up a remotes file so that you can fetch the latest from Linus' master
+branch into a local branch named "linus":
+
+ $ cat > .git/remotes/linus
+ URL: rsync://rsync.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+ Pull: master:linus
+ ^D
+
+and create the linus branch:
+
+ $ git branch linus
+
+The "linus" branch will be used to track the upstream kernel. To update it,
+you simply run:
+
+ $ git fetch linus
+
+you can do this frequently (and it should be safe to do so with pending
+work in your tree, but perhaps not if you are in mid-merge).
+
+If you need to keep track of other public trees, you can add remote branches
+for them too:
+
+ $ git branch another
+ $ cat > .git/remotes/another
+ URL: ... insert URL here ...
+ Pull: name-of-branch-in-this-remote-tree:another
+ ^D
+
+and run:
+
+ $ git fetch another
+
+Now create the branches in which you are going to work, these start
+out at the current tip of the linus branch.
+
+ $ git branch test linus
+ $ git branch release linus
+
+These can be easily kept up to date by merging from the "linus" branch:
+
+ $ git checkout test && git merge "Auto-update from upstream" test linus
+ $ git checkout release && git merge "Auto-update from upstream" release linus
+
+Set up so that you can push upstream to your public tree (you need to
+log-in to the remote system and create an empty tree there before the
+first push).
+
+ $ cat > .git/remotes/mytree
+ URL: master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux-2.6.git
+ Push: release
+ Push: test
+ ^D
+
+and the push both the test and release trees using:
+
+ $ git push mytree
+
+or push just one of the test and release branches using:
+
+ $ git push mytree test
+or
+ $ git push mytree release
+
+Now to apply some patches from the community. Think of a short
+snappy name for a branch to hold this patch (or related group of
+patches), and create a new branch from the current tip of the
+linus branch:
+
+ $ git checkout -b speed-up-spinlocks linus
+
+Now you apply the patch(es), run some tests, and commit the change(s). If
+the patch is a multi-part series, then you should apply each as a separate
+commit to this branch.
+
+ $ ... patch ... test ... commit [ ... patch ... test ... commit ]*
+
+When you are happy with the state of this change, you can pull it into the
+"test" branch in preparation to make it public:
+
+ $ git checkout test && git merge "Pull speed-up-spinlock changes" test speed-up-spinlocks
+
+It is unlikely that you would have any conflicts here ... but you might if you
+spent a while on this step and had also pulled new versions from upstream.
+
+Some time later when enough time has passed and testing done, you can pull the
+same branch into the "release" tree ready to go upstream. This is where you
+see the value of keeping each patch (or patch series) in its own branch. It
+means that the patches can be moved into the "release" tree in any order.
+
+ $ git checkout release && git merge "Pull speed-up-spinlock changes" release speed-up-spinlocks
+
+After a while, you will have a number of branches, and despite the
+well chosen names you picked for each of them, you may forget what
+they are for, or what status they are in. To get a reminder of what
+changes are in a specific branch, use:
+
+ $ git-whatchanged branchname ^linus | git-shortlog
+
+To see whether it has already been merged into the test or release branches
+use:
+
+ $ git-rev-list branchname ^test
+or
+ $ git-rev-list branchname ^release
+
+[If this branch has not yet been merged you will see a set of SHA1 values
+for the commits, if it has been merged, then there will be no output]
+
+Once a patch completes the great cycle (moving from test to release, then
+pulled by Linus, and finally coming back into your local "linus" branch)
+the branch for this change is no longer needed. You detect this when the
+output from:
+
+ $ git-rev-list branchname ^linus
+
+is empty. At this point the branch can be deleted:
+
+ $ git branch -d branchname
+
+Some changes are so trivial that it is not necessary to create a separate
+branch and then merge into each of the test and release branches. For
+these changes, just apply directly to the "release" branch, and then
+merge that into the "test" branch.
+
+To create diffstat and shortlog summaries of changes to include in a "please
+pull" request to Linus you can use:
+
+ $ git-whatchanged -p release ^linus | diffstat -p1
+and
+ $ git-whatchanged release ^linus | git-shortlog
+
+
+Here are some of the scripts that I use to simplify all this even further.
+
+==== update script ====
+# Update a branch in my GIT tree. If the branch to be updated
+# is "linus", then pull from kernel.org. Otherwise merge local
+# linus branch into test|release branch
+
+case "$1" in
+test|release)
+ git checkout $1 && git merge "Auto-update from upstream" $1 linus
+ ;;
+linus)
+ before=$(cat .git/refs/heads/linus)
+ git fetch linus
+ after=$(cat .git/refs/heads/linus)
+ if [ $before != $after ]
+ then
+ git-whatchanged $after ^$before | git-shortlog
+ fi
+ ;;
+*)
+ echo "Usage: $0 linus|test|release" 1>&2
+ exit 1
+ ;;
+esac
+
+==== merge script ====
+# Merge a branch into either the test or release branch
+
+pname=$0
+
+usage()
+{
+ echo "Usage: $pname branch test|release" 1>&2
+ exit 1
+}
+
+if [ ! -f .git/refs/heads/"$1" ]
+then
+ echo "Can't see branch <$1>" 1>&2
+ usage
+fi
+
+case "$2" in
+test|release)
+ if [ $(git-rev-list $1 ^$2 | wc -c) -eq 0 ]
+ then
+ echo $1 already merged into $2 1>&2
+ exit 1
+ fi
+ git checkout $2 && git merge "Pull $1 into $2 branch" $2 $1
+ ;;
+*)
+ usage
+ ;;
+esac
+
+==== status script ====
+# report on status of my ia64 GIT tree
+
+gb=$(tput setab 2)
+rb=$(tput setab 1)
+restore=$(tput setab 9)
+
+if [ `git-rev-list release ^test | wc -c` -gt 0 ]
+then
+ echo $rb Warning: commits in release that are not in test $restore
+ git-whatchanged release ^test
+fi
+
+for branch in `ls .git/refs/heads`
+do
+ if [ $branch = linus -o $branch = test -o $branch = release ]
+ then
+ continue
+ fi
+
+ echo -n $gb ======= $branch ====== $restore " "
+ status=
+ for ref in test release linus
+ do
+ if [ `git-rev-list $branch ^$ref | wc -c` -gt 0 ]
+ then
+ status=$status${ref:0:1}
+ fi
+ done
+ case $status in
+ trl)
+ echo $rb Need to pull into test $restore
+ ;;
+ rl)
+ echo "In test"
+ ;;
+ l)
+ echo "Waiting for linus"
+ ;;
+ "")
+ echo $rb All done $restore
+ ;;
+ *)
+ echo $rb "<$status>" $restore
+ ;;
+ esac
+ git-whatchanged $branch ^linus | git-shortlog
+done
diff --git a/Documentation/install-webdoc.sh b/Documentation/install-webdoc.sh
new file mode 100755
index 000000000..50638c78d
--- /dev/null
+++ b/Documentation/install-webdoc.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+T="$1"
+
+for h in *.html *.txt howto/*.txt howto/*.html
+do
+ diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h" || {
+ echo >&2 "# install $h $T/$h"
+ rm -f "$T/$h"
+ mkdir -p `dirname "$T/$h"`
+ cp "$h" "$T/$h"
+ }
+done
+strip_leading=`echo "$T/" | sed -e 's|.|.|g'`
+for th in "$T"/*.html "$T"/*.txt "$T"/howto/*.txt "$T"/howto/*.html
+do
+ h=`expr "$th" : "$strip_leading"'\(.*\)'`
+ case "$h" in
+ index.html) continue ;;
+ esac
+ test -f "$h" && continue
+ echo >&2 "# rm -f $th"
+ rm -f "$th"
+done
+ln -sf git.html "$T/index.html"
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
new file mode 100644
index 000000000..eebaf3aaf
--- /dev/null
+++ b/Documentation/merge-options.txt
@@ -0,0 +1,16 @@
+-n, \--no-summary::
+ Do not show diffstat at the end of the merge.
+
+--no-commit::
+ Perform the merge but pretend the merge failed and do
+ not autocommit, to give the user a chance to inspect and
+ further tweak the merge result before committing.
+
+
+-s <strategy>, \--strategy=<strategy>::
+ Use the given merge strategy; can be supplied more than
+ once to specify them in the order they should be tried.
+ If there is no `-s` option, a built-in list of strategies
+ is used instead (`git-merge-resolve` when merging a single
+ head, `git-merge-octopus` otherwise).
+
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
new file mode 100644
index 000000000..3ec56d22e
--- /dev/null
+++ b/Documentation/merge-strategies.txt
@@ -0,0 +1,35 @@
+MERGE STRATEGIES
+----------------
+
+resolve::
+ This can only resolve two heads (i.e. the current branch
+ and another branch you pulled from) using 3-way merge
+ algorithm. It tries to carefully detect criss-cross
+ merge ambiguities and is considered generally safe and
+ fast. This is the default merge strategy when pulling
+ one branch.
+
+recursive::
+ This can only resolve two heads using 3-way merge
+ algorithm. When there are more than one common
+ ancestors that can be used for 3-way merge, it creates a
+ merged tree of the common ancestores and uses that as
+ the reference tree for the 3-way merge. This has been
+ reported to result in fewer merge conflicts without
+ causing mis-merges by tests done on actual merge commits
+ taken from Linux 2.6 kernel development history.
+ Additionally this can detect and handle merges involving
+ renames.
+
+octopus::
+ This resolves more than two-head case, but refuses to do
+ complex merge that needs manual resolution. It is
+ primarily meant to be used for bundling topic branch
+ heads together. This is the default merge strategy when
+ pulling more than one branch.
+
+ours::
+ This resolves any number of heads, but the result of the
+ merge is always the current branch head. It is meant to
+ be used to supersede old development history of side
+ branches.
diff --git a/Documentation/pack-protocol.txt b/Documentation/pack-protocol.txt
new file mode 100644
index 000000000..7d6aec409
--- /dev/null
+++ b/Documentation/pack-protocol.txt
@@ -0,0 +1,38 @@
+There are two Pack push-pull protocols.
+
+upload-pack (S) | fetch/clone-pack (C) protocol:
+
+ # Tell the puller what commits we have and what their names are
+ S: SHA1 name
+ S: ...
+ S: SHA1 name
+ S: # flush -- it's your turn
+ # Tell the pusher what commits we want, and what we have
+ C: want name
+ C: ..
+ C: want name
+ C: have SHA1
+ C: have SHA1
+ C: ...
+ C: # flush -- occasionally ask "had enough?"
+ S: NAK
+ C: have SHA1
+ C: ...
+ C: have SHA1
+ S: ACK
+ C: done
+ S: XXXXXXX -- packfile contents.
+
+send-pack | receive-pack protocol.
+
+ # Tell the pusher what commits we have and what their names are
+ C: SHA1 name
+ C: ...
+ C: SHA1 name
+ C: # flush -- it's your turn
+ # Tell the puller what the pusher has
+ S: old-SHA1 new-SHA1 name
+ S: old-SHA1 new-SHA1 name
+ S: ...
+ S: # flush -- done with the list
+ S: XXXXXXX --- packfile contents.
diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt
new file mode 100644
index 000000000..ddd5823df
--- /dev/null
+++ b/Documentation/pull-fetch-param.txt
@@ -0,0 +1,124 @@
+<repository>::
+ The "remote" repository that is the source of a fetch
+ or pull operation, or the destination of a push operation.
+ One of the following notations can be used
+ to name the remote repository:
++
+===============================================================
+- Rsync URL: rsync://remote.machine/path/to/repo.git/
+- HTTP(s) URL: http://remote.machine/path/to/repo.git/
+- git URL: git://remote.machine/path/to/repo.git/
+- ssh URL: remote.machine:/path/to/repo.git/
+- Local directory: /path/to/repo.git/
+===============================================================
++
+In addition to the above, as a short-hand, the name of a
+file in `$GIT_DIR/remotes` directory can be given; the
+named file should be in the following format:
++
+ URL: one of the above URL format
+ Push: <refspec>
+ Pull: <refspec>
++
+When such a short-hand is specified in place of
+<repository> without <refspec> parameters on the command
+line, <refspec> specified on `Push:` lines or `Pull:`
+lines are used for `git-push` and `git-fetch`/`git-pull`,
+respectively. Multiple `Push:` and and `Pull:` lines may
+be specified for additional branch mappings.
++
+The name of a file in `$GIT_DIR/branches` directory can be
+specified as an older notation short-hand; the named
+file should contain a single line, a URL in one of the
+above formats, optionally followed by a hash `#` and the
+name of remote head (URL fragment notation).
+`$GIT_DIR/branches/<remote>` file that stores a <url>
+without the fragment is equivalent to have this in the
+corresponding file in the `$GIT_DIR/remotes/` directory.
++
+ URL: <url>
+ Pull: refs/heads/master:<remote>
++
+while having `<url>#<head>` is equivalent to
++
+ URL: <url>
+ Pull: refs/heads/<head>:<remote>
+
+<refspec>::
+ The canonical format of a <refspec> parameter is
+ `+?<src>:<dst>`; that is, an optional plus `+`, followed
+ by the source ref, followed by a colon `:`, followed by
+ the destination ref.
++
+When used in `git-push`, the <src> side can be an
+arbitrary "SHA1 expression" that can be used as an
+argument to `git-cat-file -t`. E.g. `master~4` (push
+four parents before the current master head).
++
+For `git-push`, the local ref that matches <src> is used
+to fast forward the remote ref that matches <dst>. If
+the optional plus `+` is used, the remote ref is updated
+even if it does not result in a fast forward update.
++
+For `git-fetch` and `git-pull`, the remote ref that matches <src>
+is fetched, and if <dst> is not empty string, the local
+ref that matches it is fast forwarded using <src>.
+Again, if the optional plus `+` is used, the local ref
+is updated even if it does not result in a fast forward
+update.
++
+[NOTE]
+If the remote branch from which you want to pull is
+modified in non-linear ways such as being rewound and
+rebased frequently, then a pull will attempt a merge with
+an older version of itself, likely conflict, and fail.
+It is under these conditions that you would want to use
+the `+` sign to indicate non-fast-forward updates will
+be needed. There is currently no easy way to determine
+or declare that a branch will be made available in a
+repository with this behavior; the pulling user simply
+must know this is the expected usage pattern for a branch.
++
+[NOTE]
+You never do your own development on branches that appear
+on the right hand side of a <refspec> colon on `Pull:` lines;
+they are to be updated by `git-fetch`. If you intend to do
+development derived from a remote branch `B`, have a `Pull:`
+line to track it (i.e. `Pull: B:remote-B`), and have a separate
+branch `my-B` to do your development on top of it. The latter
+is created by `git branch my-B remote-B` (or its equivalent `git
+checkout -b my-B remote-B`). Run `git fetch` to keep track of
+the progress of the remote side, and when you see something new
+on the remote branch, merge it into your development branch with
+`git pull . remote-B`, while you are on `my-B` branch.
+The common `Pull: master:origin` mapping of a remote `master`
+branch to a local `origin` branch, which is then merged to a
+ocal development branch, again typically named `master`, is made
+when you run `git clone` for you to follow this pattern.
++
+[NOTE]
+There is a difference between listing multiple <refspec>
+directly on `git-pull` command line and having multiple
+`Pull:` <refspec> lines for a <repository> and running
+`git-pull` command without any explicit <refspec> parameters.
+<refspec> listed explicitly on the command line are always
+merged into the current branch after fetching. In other words,
+if you list more than one remote refs, you would be making
+an Octopus. While `git-pull` run without any explicit <refspec>
+parameter takes default <refspec>s from `Pull:` lines, it
+merges only the first <refspec> found into the current branch,
+after fetching all the remote refs. This is because making an
+Octopus from remote refs is rarely done, while keeping track
+of multiple remote heads in one-go by fetching more than one
+is often useful.
++
+Some short-cut notations are also supported.
++
+* For backward compatibility, `tag` is almost ignored;
+ it just makes the following parameter <tag> to mean a
+ refspec `refs/tags/<tag>:refs/tags/<tag>`.
+* A parameter <ref> without a colon is equivalent to
+ <ref>: when pulling/fetching, and <ref>`:`<ref> when
+ pushing. That is, do not store it locally if
+ fetching, and update the same name if pushing.
+
diff --git a/Documentation/repository-layout.txt b/Documentation/repository-layout.txt
new file mode 100644
index 000000000..1b5f22824
--- /dev/null
+++ b/Documentation/repository-layout.txt
@@ -0,0 +1,128 @@
+git repository layout
+=====================
+
+You may find these things in your git repository (`.git`
+directory for a repository associated with your working tree, or
+`'project'.git` directory for a public 'naked' repository).
+
+objects::
+ Object store associated with this repository. Usually
+ an object store is self sufficient (i.e. all the objects
+ that are referred to by an object found in it are also
+ found in it), but there are couple of ways to violate
+ it.
++
+. You could populate the repository by running a commit walker
+without `-a` option. Depending on which options are given, you
+could have only commit objects without associated blobs and
+trees this way, for example. A repository with this kind of
+incomplete object store is not suitable to be published to the
+outside world but sometimes useful for private repository.
+. You can be using `objects/info/alternates` mechanism, or
+`$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanism to 'borrow'
+objects from other object stores. A repository with this kind
+of incompete object store is not suitable to be published for
+use with dumb transports but otherwise is OK as long as
+`objects/info/alternates` points at the right object stores
+it borrows from.
+
+objects/[0-9a-f][0-9a-f]::
+ Traditionally, each object is stored in its own file.
+ They are split into 256 subdirectories using the first
+ two letters from its object name to keep the number of
+ directory entries `objects` directory itself needs to
+ hold. Objects found here are often called 'unpacked'
+ objects.
+
+objects/pack::
+ Packs (files that store many object in compressed form,
+ along with index files to allow them to be randomly
+ accessed) are found in this directory.
+
+objects/info::
+ Additional information about the object store is
+ recorded in this directory.
+
+objects/info/packs::
+ This file is to help dumb transports discover what packs
+ are available in this object store. Whenever a pack is
+ added or removed, `git update-server-info` should be run
+ to keep this file up-to-date if the repository is
+ published for dumb transports. `git repack` does this
+ by default.
+
+objects/info/alternates::
+ This file records absolute filesystem paths of alternate
+ object stores that this object store borrows objects
+ from, one pathname per line.
+
+refs::
+ References are stored in subdirectories of this
+ directory. The `git prune` command knows to keep
+ objects reachable from refs found in this directory and
+ its subdirectories.
+
+refs/heads/`name`::
+ records tip-of-the-tree commit objects of branch `name`
+
+refs/tags/`name`::
+ records any object name (not necessarily a commit
+ object, or a tag object that points at a commit object).
+
+HEAD::
+ A symlink of the form `refs/heads/'name'` to point at
+ the current branch, if exists. It does not mean much if
+ the repository is not associated with any working tree
+ (i.e. 'naked' repository), but a valid git repository
+ *must* have such a symlink here. It is legal if the
+ named branch 'name' does not (yet) exist.
+
+branches::
+ A slightly deprecated way to store shorthands to be used
+ to specify URL to `git fetch`, `git pull` and `git push`
+ commands is to store a file in `branches/'name'` and
+ give 'name' to these commands in place of 'repository'
+ argument.
+
+hooks::
+ Hooks are customization scripts used by various git
+ commands. A handful of sample hooks are installed when
+ `git init-db` is run, but all of them are disabled by
+ default. To enable, they need to be made executable.
+
+index::
+ The current index file for the repository. It is
+ usually not found in a naked repository.
+
+info::
+ Additional information about the repository is recorded
+ in this directory.
+
+info/refs::
+ This file is to help dumb transports to discover what
+ refs are available in this repository. Whenever you
+ create/delete a new branch or a new tag, `git
+ update-server-info` should be run to keep this file
+ up-to-date if the repository is published for dumb
+ transports. The `git-receive-pack` command, which is
+ run on a remote repository when you `git push` into it,
+ runs `hooks/update` hook to help you achive this.
+
+info/grafts::
+ This file records fake commit ancestry information, to
+ pretend the set of parents a commit has is different
+ from how the commit was actually created. One record
+ per line describes a commit and its fake parents by
+ listing their 40-byte hexadecimal object names separated
+ by a space and terminated by a newline.
+
+info/exclude::
+ This file, by convention among Porcelains, stores the
+ exclude pattern list. `git status` looks at it, but
+ otherwise it is not looked at by any of the core git
+ commands.
+
+remotes::
+ Stores shorthands to be used to give URL and default
+ refnames to interact with remote repository to `git
+ fetch`, `git pull` and `git push` commands.
diff --git a/Documentation/sort_glossary.pl b/Documentation/sort_glossary.pl
new file mode 100644
index 000000000..babbea041
--- /dev/null
+++ b/Documentation/sort_glossary.pl
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+
+%terms=();
+
+while(<>) {
+ if(/^(\S.*)::$/) {
+ my $term=$1;
+ if(defined($terms{$term})) {
+ die "$1 defined twice\n";
+ }
+ $terms{$term}="";
+ LOOP: while(<>) {
+ if(/^$/) {
+ last LOOP;
+ }
+ if(/^ \S/) {
+ $terms{$term}.=$_;
+ } else {
+ die "Error 1: $_";
+ }
+ }
+ }
+}
+
+sub format_tab_80 ($) {
+ my $text=$_[0];
+ my $result="";
+ $text=~s/\s+/ /g;
+ $text=~s/^\s+//;
+ while($text=~/^(.{1,72})(|\s+(\S.*)?)$/) {
+ $result.=" ".$1."\n";
+ $text=$3;
+ }
+ return $result;
+}
+
+sub no_spaces ($) {
+ my $result=$_[0];
+ $result=~tr/ /_/;
+ return $result;
+}
+
+print 'GIT Glossary
+============
+Aug 2005
+
+This list is sorted alphabetically:
+
+';
+
+@keys=sort {uc($a) cmp uc($b)} keys %terms;
+$pattern='(\b'.join('\b|\b',reverse @keys).'\b)';
+foreach $key (@keys) {
+ $terms{$key}=~s/$pattern/sprintf "<<ref_".no_spaces($1).",$1>>";/eg;
+ print '[[ref_'.no_spaces($key).']]'.$key."::\n"
+ .format_tab_80($terms{$key})."\n";
+}
+
+print '
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de> and
+the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the link:git.html[git] suite
+';
+
diff --git a/Documentation/technical/trivial-merge.txt b/Documentation/technical/trivial-merge.txt
new file mode 100644
index 000000000..24c84100b
--- /dev/null
+++ b/Documentation/technical/trivial-merge.txt
@@ -0,0 +1,121 @@
+Trivial merge rules
+===================
+
+This document describes the outcomes of the trivial merge logic in read-tree.
+
+One-way merge
+-------------
+
+This replaces the index with a different tree, keeping the stat info
+for entries that don't change, and allowing -u to make the minimum
+required changes to the working tree to have it match.
+
+Entries marked '+' have stat information. Spaces marked '*' don't
+affect the result.
+
+ index tree result
+ -----------------------
+ * (empty) (empty)
+ (empty) tree tree
+ index+ tree tree
+ index+ index index+
+
+Two-way merge
+-------------
+
+It is permitted for the index to lack an entry; this does not prevent
+any case from applying.
+
+If the index exists, it is an error for it not to match either the old
+or the result.
+
+If multiple cases apply, the one used is listed first.
+
+A result which changes the index is an error if the index is not empty
+and not up-to-date.
+
+Entries marked '+' have stat information. Spaces marked '*' don't
+affect the result.
+
+ case index old new result
+ -------------------------------------
+ 0/2 (empty) * (empty) (empty)
+ 1/3 (empty) * new new
+ 4/5 index+ (empty) (empty) index+
+ 6/7 index+ (empty) index index+
+ 10 index+ index (empty) (empty)
+ 14/15 index+ old old index+
+ 18/19 index+ old index index+
+ 20 index+ index new new
+
+Three-way merge
+---------------
+
+It is permitted for the index to lack an entry; this does not prevent
+any case from applying.
+
+If the index exists, it is an error for it not to match either the
+head or (if the merge is trivial) the result.
+
+If multiple cases apply, the one used is listed first.
+
+A result of "no merge" means that index is left in stage 0, ancest in
+stage 1, head in stage 2, and remote in stage 3 (if any of these are
+empty, no entry is left for that stage). Otherwise, the given entry is
+left in stage 0, and there are no other entries.
+
+A result of "no merge" is an error if the index is not empty and not
+up-to-date.
+
+*empty* means that the tree must not have a directory-file conflict
+ with the entry.
+
+For multiple ancestors, a '+' means that this case applies even if
+only one ancestor or remote fits; a '^' means all of the ancestors
+must be the same.
+
+case ancest head remote result
+----------------------------------------
+1 (empty)+ (empty) (empty) (empty)
+2ALT (empty)+ *empty* remote remote
+2 (empty)^ (empty) remote no merge
+3ALT (empty)+ head *empty* head
+3 (empty)^ head (empty) no merge
+4 (empty)^ head remote no merge
+5ALT * head head head
+6 ancest+ (empty) (empty) no merge
+8 ancest^ (empty) ancest no merge
+7 ancest+ (empty) remote no merge
+10 ancest^ ancest (empty) no merge
+9 ancest+ head (empty) no merge
+16 anc1/anc2 anc1 anc2 no merge
+13 ancest+ head ancest head
+14 ancest+ ancest remote remote
+11 ancest+ head remote no merge
+
+Only #2ALT and #3ALT use *empty*, because these are the only cases
+where there can be conflicts that didn't exist before. Note that we
+allow directory-file conflicts between things in different stages
+after the trivial merge.
+
+A possible alternative for #6 is (empty), which would make it like
+#1. This is not used, due to the likelihood that it arises due to
+moving the file to multiple different locations or moving and deleting
+it in different branches.
+
+Case #1 is included for completeness, and also in case we decide to
+put on '+' markings; any path that is never mentioned at all isn't
+handled.
+
+Note that #16 is when both #13 and #14 apply; in this case, we refuse
+the trivial merge, because we can't tell from this data which is
+right. This is a case of a reverted patch (in some direction, maybe
+multiple times), and the right answer depends on looking at crossings
+of history or common ancestors of the ancestors.
+
+Note that, between #6, #7, #9, and #11, all cases not otherwise
+covered are handled in this table.
+
+For #8 and #10, there is alternative behavior, not currently
+implemented, where the result is (empty). As currently implemented,
+the automatic merge will generally give this effect.
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
new file mode 100644
index 000000000..03eb4216f
--- /dev/null
+++ b/Documentation/tutorial.txt
@@ -0,0 +1,1734 @@
+A short git tutorial
+====================
+
+Introduction
+------------
+
+This is trying to be a short tutorial on setting up and using a git
+repository, mainly because being hands-on and using explicit examples is
+often the best way of explaining what is going on.
+
+In normal life, most people wouldn't use the "core" git programs
+directly, but rather script around them to make them more palatable.
+Understanding the core git stuff may help some people get those scripts
+done, though, and it may also be instructive in helping people
+understand what it is that the higher-level helper scripts are actually
+doing.
+
+The core git is often called "plumbing", with the prettier user
+interfaces on top of it called "porcelain". You may not want to use the
+plumbing directly very often, but it can be good to know what the
+plumbing does for when the porcelain isn't flushing...
+
+
+Creating a git repository
+-------------------------
+
+Creating a new git repository couldn't be easier: all git repositories start
+out empty, and the only thing you need to do is find yourself a
+subdirectory that you want to use as a working tree - either an empty
+one for a totally new project, or an existing working tree that you want
+to import into git.
+
+For our first example, we're going to start a totally new repository from
+scratch, with no pre-existing files, and we'll call it `git-tutorial`.
+To start up, create a subdirectory for it, change into that
+subdirectory, and initialize the git infrastructure with `git-init-db`:
+
+------------------------------------------------
+$ mkdir git-tutorial
+$ cd git-tutorial
+$ git-init-db
+------------------------------------------------
+
+to which git will reply
+
+----------------
+defaulting to local storage area
+----------------
+
+which is just git's way of saying that you haven't been doing anything
+strange, and that it will have created a local `.git` directory setup for
+your new project. You will now have a `.git` directory, and you can
+inspect that with `ls`. For your new empty project, it should show you
+three entries, among other things:
+
+ - a symlink called `HEAD`, pointing to `refs/heads/master` (if your
+ platform does not have native symlinks, it is a file containing the
+ line "ref: refs/heads/master")
++
+Don't worry about the fact that the file that the `HEAD` link points to
+doesn't even exist yet -- you haven't created the commit that will
+start your `HEAD` development branch yet.
+
+ - a subdirectory called `objects`, which will contain all the
+ objects of your project. You should never have any real reason to
+ look at the objects directly, but you might want to know that these
+ objects are what contains all the real 'data' in your repository.
+
+ - a subdirectory called `refs`, which contains references to objects.
+
+In particular, the `refs` subdirectory will contain two other
+subdirectories, named `heads` and `tags` respectively. They do
+exactly what their names imply: they contain references to any number
+of different 'heads' of development (aka 'branches'), and to any
+'tags' that you have created to name specific versions in your
+repository.
+
+One note: the special `master` head is the default branch, which is
+why the `.git/HEAD` file was created as a symlink to it even if it
+doesn't yet exist. Basically, the `HEAD` link is supposed to always
+point to the branch you are working on right now, and you always
+start out expecting to work on the `master` branch.
+
+However, this is only a convention, and you can name your branches
+anything you want, and don't have to ever even 'have' a `master`
+branch. A number of the git tools will assume that `.git/HEAD` is
+valid, though.
+
+[NOTE]
+An 'object' is identified by its 160-bit SHA1 hash, aka 'object name',
+and a reference to an object is always the 40-byte hex
+representation of that SHA1 name. The files in the `refs`
+subdirectory are expected to contain these hex references
+(usually with a final `\'\n\'` at the end), and you should thus
+expect to see a number of 41-byte files containing these
+references in these `refs` subdirectories when you actually start
+populating your tree.
+
+[NOTE]
+An advanced user may want to take a look at the
+link:repository-layout.html[repository layout] document
+after finishing this tutorial.
+
+You have now created your first git repository. Of course, since it's
+empty, that's not very useful, so let's start populating it with data.
+
+
+Populating a git repository
+---------------------------
+
+We'll keep this simple and stupid, so we'll start off with populating a
+few trivial files just to get a feel for it.
+
+Start off with just creating any random files that you want to maintain
+in your git repository. We'll start off with a few bad examples, just to
+get a feel for how this works:
+
+------------------------------------------------
+$ echo "Hello World" >hello
+$ echo "Silly example" >example
+------------------------------------------------
+
+you have now created two files in your working tree (aka 'working directory'), but to
+actually check in your hard work, you will have to go through two steps:
+
+ - fill in the 'index' file (aka 'cache') with the information about your
+ working tree state.
+
+ - commit that index file as an object.
+
+The first step is trivial: when you want to tell git about any changes
+to your working tree, you use the `git-update-index` program. That
+program normally just takes a list of filenames you want to update, but
+to avoid trivial mistakes, it refuses to add new entries to the index
+(or remove existing ones) unless you explicitly tell it that you're
+adding a new entry with the `\--add` flag (or removing an entry with the
+`\--remove`) flag.
+
+So to populate the index with the two files you just created, you can do
+
+------------------------------------------------
+$ git-update-index --add hello example
+------------------------------------------------
+
+and you have now told git to track those two files.
+
+In fact, as you did that, if you now look into your object directory,
+you'll notice that git will have added two new objects to the object
+database. If you did exactly the steps above, you should now be able to do
+
+
+----------------
+$ ls .git/objects/??/*
+----------------
+
+and see two files:
+
+----------------
+.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
+.git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
+----------------
+
+which correspond with the objects with names of 557db... and f24c7..
+respectively.
+
+If you want to, you can use `git-cat-file` to look at those objects, but
+you'll have to use the object name, not the filename of the object:
+
+----------------
+$ git-cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
+----------------
+
+where the `-t` tells `git-cat-file` to tell you what the "type" of the
+object is. git will tell you that you have a "blob" object (ie just a
+regular file), and you can see the contents with
+
+----------------
+$ git-cat-file "blob" 557db03
+----------------
+
+which will print out "Hello World". The object 557db03 is nothing
+more than the contents of your file `hello`.
+
+[NOTE]
+Don't confuse that object with the file `hello` itself. The
+object is literally just those specific *contents* of the file, and
+however much you later change the contents in file `hello`, the object
+we just looked at will never change. Objects are immutable.
+
+[NOTE]
+The second example demonstrates that you can
+abbreviate the object name to only the first several
+hexadecimal digits in most places.
+
+Anyway, as we mentioned previously, you normally never actually take a
+look at the objects themselves, and typing long 40-character hex
+names is not something you'd normally want to do. The above digression
+was just to show that `git-update-index` did something magical, and
+actually saved away the contents of your files into the git object
+database.
+
+Updating the index did something else too: it created a `.git/index`
+file. This is the index that describes your current working tree, and
+something you should be very aware of. Again, you normally never worry
+about the index file itself, but you should be aware of the fact that
+you have not actually really "checked in" your files into git so far,
+you've only *told* git about them.
+
+However, since git knows about them, you can now start using some of the
+most basic git commands to manipulate the files or look at their status.
+
+In particular, let's not even check in the two files into git yet, we'll
+start off by adding another line to `hello` first:
+
+------------------------------------------------
+$ echo "It's a new day for git" >>hello
+------------------------------------------------
+
+and you can now, since you told git about the previous state of `hello`, ask
+git what has changed in the tree compared to your old index, using the
+`git-diff-files` command:
+
+------------
+$ git-diff-files
+------------
+
+Oops. That wasn't very readable. It just spit out its own internal
+version of a `diff`, but that internal version really just tells you
+that it has noticed that "hello" has been modified, and that the old object
+contents it had have been replaced with something else.
+
+To make it readable, we can tell git-diff-files to output the
+differences as a patch, using the `-p` flag:
+
+------------
+$ git-diff-files -p
+diff --git a/hello b/hello
+index 557db03..263414f 100644
+--- a/hello
++++ b/hello
+@@ -1 +1,2 @@
+ Hello World
++It's a new day for git
+----
+
+i.e. the diff of the change we caused by adding another line to `hello`.
+
+In other words, `git-diff-files` always shows us the difference between
+what is recorded in the index, and what is currently in the working
+tree. That's very useful.
+
+A common shorthand for `git-diff-files -p` is to just write `git
+diff`, which will do the same thing.
+
+
+Committing git state
+--------------------
+
+Now, we want to go to the next stage in git, which is to take the files
+that git knows about in the index, and commit them as a real tree. We do
+that in two phases: creating a 'tree' object, and committing that 'tree'
+object as a 'commit' object together with an explanation of what the
+tree was all about, along with information of how we came to that state.
+
+Creating a tree object is trivial, and is done with `git-write-tree`.
+There are no options or other input: git-write-tree will take the
+current index state, and write an object that describes that whole
+index. In other words, we're now tying together all the different
+filenames with their contents (and their permissions), and we're
+creating the equivalent of a git "directory" object:
+
+------------------------------------------------
+$ git-write-tree
+------------------------------------------------
+
+and this will just output the name of the resulting tree, in this case
+(if you have done exactly as I've described) it should be
+
+----------------
+8988da15d077d4829fc51d8544c097def6644dbb
+----------------
+
+which is another incomprehensible object name. Again, if you want to,
+you can use `git-cat-file -t 8988d\...` to see that this time the object
+is not a "blob" object, but a "tree" object (you can also use
+`git-cat-file` to actually output the raw object contents, but you'll see
+mainly a binary mess, so that's less interesting).
+
+However -- normally you'd never use `git-write-tree` on its own, because
+normally you always commit a tree into a commit object using the
+`git-commit-tree` command. In fact, it's easier to not actually use
+`git-write-tree` on its own at all, but to just pass its result in as an
+argument to `git-commit-tree`.
+
+`git-commit-tree` normally takes several arguments -- it wants to know
+what the 'parent' of a commit was, but since this is the first commit
+ever in this new repository, and it has no parents, we only need to pass in
+the object name of the tree. However, `git-commit-tree`
+also wants to get a commit message
+on its standard input, and it will write out the resulting object name for the
+commit to its standard output.
+
+And this is where we create the `.git/refs/heads/master` file
+which is pointed at by `HEAD`. This file is supposed to contain
+the reference to the top-of-tree of the master branch, and since
+that's exactly what `git-commit-tree` spits out, we can do this
+all with a sequence of simple shell commands:
+
+------------------------------------------------
+$ tree=$(git-write-tree)
+$ commit=$(echo 'Initial commit' | git-commit-tree $tree)
+$ git-update-ref HEAD $commit
+------------------------------------------------
+
+which will say:
+
+----------------
+Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb
+----------------
+
+just to warn you about the fact that it created a totally new commit
+that is not related to anything else. Normally you do this only *once*
+for a project ever, and all later commits will be parented on top of an
+earlier commit, and you'll never see this "Committing initial tree"
+message ever again.
+
+Again, normally you'd never actually do this by hand. There is a
+helpful script called `git commit` that will do all of this for you. So
+you could have just written `git commit`
+instead, and it would have done the above magic scripting for you.
+
+
+Making a change
+---------------
+
+Remember how we did the `git-update-index` on file `hello` and then we
+changed `hello` afterward, and could compare the new state of `hello` with the
+state we saved in the index file?
+
+Further, remember how I said that `git-write-tree` writes the contents
+of the *index* file to the tree, and thus what we just committed was in
+fact the *original* contents of the file `hello`, not the new ones. We did
+that on purpose, to show the difference between the index state, and the
+state in the working tree, and how they don't have to match, even
+when we commit things.
+
+As before, if we do `git-diff-files -p` in our git-tutorial project,
+we'll still see the same difference we saw last time: the index file
+hasn't changed by the act of committing anything. However, now that we
+have committed something, we can also learn to use a new command:
+`git-diff-index`.
+
+Unlike `git-diff-files`, which showed the difference between the index
+file and the working tree, `git-diff-index` shows the differences
+between a committed *tree* and either the index file or the working
+tree. In other words, `git-diff-index` wants a tree to be diffed
+against, and before we did the commit, we couldn't do that, because we
+didn't have anything to diff against.
+
+But now we can do
+
+----------------
+$ git-diff-index -p HEAD
+----------------
+
+(where `-p` has the same meaning as it did in `git-diff-files`), and it
+will show us the same difference, but for a totally different reason.
+Now we're comparing the working tree not against the index file,
+but against the tree we just wrote. It just so happens that those two
+are obviously the same, so we get the same result.
+
+Again, because this is a common operation, you can also just shorthand
+it with
+
+----------------
+$ git diff HEAD
+----------------
+
+which ends up doing the above for you.
+
+In other words, `git-diff-index` normally compares a tree against the
+working tree, but when given the `\--cached` flag, it is told to
+instead compare against just the index cache contents, and ignore the
+current working tree state entirely. Since we just wrote the index
+file to HEAD, doing `git-diff-index \--cached -p HEAD` should thus return
+an empty set of differences, and that's exactly what it does.
+
+[NOTE]
+================
+`git-diff-index` really always uses the index for its
+comparisons, and saying that it compares a tree against the working
+tree is thus not strictly accurate. In particular, the list of
+files to compare (the "meta-data") *always* comes from the index file,
+regardless of whether the `\--cached` flag is used or not. The `\--cached`
+flag really only determines whether the file *contents* to be compared
+come from the working tree or not.
+
+This is not hard to understand, as soon as you realize that git simply
+never knows (or cares) about files that it is not told about
+explicitly. git will never go *looking* for files to compare, it
+expects you to tell it what the files are, and that's what the index
+is there for.
+================
+
+However, our next step is to commit the *change* we did, and again, to
+understand what's going on, keep in mind the difference between "working
+tree contents", "index file" and "committed tree". We have changes
+in the working tree that we want to commit, and we always have to
+work through the index file, so the first thing we need to do is to
+update the index cache:
+
+------------------------------------------------
+$ git-update-index hello
+------------------------------------------------
+
+(note how we didn't need the `\--add` flag this time, since git knew
+about the file already).
+
+Note what happens to the different `git-diff-\*` versions here. After
+we've updated `hello` in the index, `git-diff-files -p` now shows no
+differences, but `git-diff-index -p HEAD` still *does* show that the
+current state is different from the state we committed. In fact, now
+`git-diff-index` shows the same difference whether we use the `--cached`
+flag or not, since now the index is coherent with the working tree.
+
+Now, since we've updated `hello` in the index, we can commit the new
+version. We could do it by writing the tree by hand again, and
+committing the tree (this time we'd have to use the `-p HEAD` flag to
+tell commit that the HEAD was the *parent* of the new commit, and that
+this wasn't an initial commit any more), but you've done that once
+already, so let's just use the helpful script this time:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+which starts an editor for you to write the commit message and tells you
+a bit about what you have done.
+
+Write whatever message you want, and all the lines that start with '#'
+will be pruned out, and the rest will be used as the commit message for
+the change. If you decide you don't want to commit anything after all at
+this point (you can continue to edit things and update the index), you
+can just leave an empty message. Otherwise `git commit` will commit
+the change for you.
+
+You've now made your first real git commit. And if you're interested in
+looking at what `git commit` really does, feel free to investigate:
+it's a few very simple shell scripts to generate the helpful (?) commit
+message headers, and a few one-liners that actually do the
+commit itself (`git-commit`).
+
+
+Inspecting Changes
+------------------
+
+While creating changes is useful, it's even more useful if you can tell
+later what changed. The most useful command for this is another of the
+`diff` family, namely `git-diff-tree`.
+
+`git-diff-tree` can be given two arbitrary trees, and it will tell you the
+differences between them. Perhaps even more commonly, though, you can
+give it just a single commit object, and it will figure out the parent
+of that commit itself, and show the difference directly. Thus, to get
+the same diff that we've already seen several times, we can now do
+
+----------------
+$ git-diff-tree -p HEAD
+----------------
+
+(again, `-p` means to show the difference as a human-readable patch),
+and it will show what the last commit (in `HEAD`) actually changed.
+
+[NOTE]
+============
+Here is an ASCII art by Jon Loeliger that illustrates how
+various diff-\* commands compare things.
+
+ diff-tree
+ +----+
+ | |
+ | |
+ V V
+ +-----------+
+ | Object DB |
+ | Backing |
+ | Store |
+ +-----------+
+ ^ ^
+ | |
+ | | diff-index --cached
+ | |
+ diff-index | V
+ | +-----------+
+ | | Index |
+ | | "cache" |
+ | +-----------+
+ | ^
+ | |
+ | | diff-files
+ | |
+ V V
+ +-----------+
+ | Working |
+ | Directory |
+ +-----------+
+============
+
+More interestingly, you can also give `git-diff-tree` the `-v` flag, which
+tells it to also show the commit message and author and date of the
+commit, and you can tell it to show a whole series of diffs.
+Alternatively, you can tell it to be "silent", and not show the diffs at
+all, but just show the actual commit message.
+
+In fact, together with the `git-rev-list` program (which generates a
+list of revisions), `git-diff-tree` ends up being a veritable fount of
+changes. A trivial (but very useful) script called `git-whatchanged` is
+included with git which does exactly this, and shows a log of recent
+activities.
+
+To see the whole history of our pitiful little git-tutorial project, you
+can do
+
+----------------
+$ git log
+----------------
+
+which shows just the log messages, or if we want to see the log together
+with the associated patches use the more complex (and much more
+powerful)
+
+----------------
+$ git-whatchanged -p --root
+----------------
+
+and you will see exactly what has changed in the repository over its
+short history.
+
+[NOTE]
+The `\--root` flag is a flag to `git-diff-tree` to tell it to
+show the initial aka 'root' commit too. Normally you'd probably not
+want to see the initial import diff, but since the tutorial project
+was started from scratch and is so small, we use it to make the result
+a bit more interesting.
+
+With that, you should now be having some inkling of what git does, and
+can explore on your own.
+
+[NOTE]
+Most likely, you are not directly using the core
+git Plumbing commands, but using Porcelain like Cogito on top
+of it. Cogito works a bit differently and you usually do not
+have to run `git-update-index` yourself for changed files (you
+do tell underlying git about additions and removals via
+`cg-add` and `cg-rm` commands). Just before you make a commit
+with `cg-commit`, Cogito figures out which files you modified,
+and runs `git-update-index` on them for you.
+
+
+Tagging a version
+-----------------
+
+In git, there are two kinds of tags, a "light" one, and an "annotated tag".
+
+A "light" tag is technically nothing more than a branch, except we put
+it in the `.git/refs/tags/` subdirectory instead of calling it a `head`.
+So the simplest form of tag involves nothing more than
+
+------------------------------------------------
+$ git tag my-first-tag
+------------------------------------------------
+
+which just writes the current `HEAD` into the `.git/refs/tags/my-first-tag`
+file, after which point you can then use this symbolic name for that
+particular state. You can, for example, do
+
+----------------
+$ git diff my-first-tag
+----------------
+
+to diff your current state against that tag (which at this point will
+obviously be an empty diff, but if you continue to develop and commit
+stuff, you can use your tag as an "anchor-point" to see what has changed
+since you tagged it.
+
+An "annotated tag" is actually a real git object, and contains not only a
+pointer to the state you want to tag, but also a small tag name and
+message, along with optionally a PGP signature that says that yes,
+you really did
+that tag. You create these annotated tags with either the `-a` or
+`-s` flag to `git tag`:
+
+----------------
+$ git tag -s <tagname>
+----------------
+
+which will sign the current `HEAD` (but you can also give it another
+argument that specifies the thing to tag, ie you could have tagged the
+current `mybranch` point by using `git tag <tagname> mybranch`).
+
+You normally only do signed tags for major releases or things
+like that, while the light-weight tags are useful for any marking you
+want to do -- any time you decide that you want to remember a certain
+point, just create a private tag for it, and you have a nice symbolic
+name for the state at that point.
+
+
+Copying repositories
+--------------------
+
+git repositories are normally totally self-sufficient and relocatable
+Unlike CVS, for example, there is no separate notion of
+"repository" and "working tree". A git repository normally *is* the
+working tree, with the local git information hidden in the `.git`
+subdirectory. There is nothing else. What you see is what you got.
+
+[NOTE]
+You can tell git to split the git internal information from
+the directory that it tracks, but we'll ignore that for now: it's not
+how normal projects work, and it's really only meant for special uses.
+So the mental model of "the git information is always tied directly to
+the working tree that it describes" may not be technically 100%
+accurate, but it's a good model for all normal use.
+
+This has two implications:
+
+ - if you grow bored with the tutorial repository you created (or you've
+ made a mistake and want to start all over), you can just do simple
++
+----------------
+$ rm -rf git-tutorial
+----------------
++
+and it will be gone. There's no external repository, and there's no
+history outside the project you created.
+
+ - if you want to move or duplicate a git repository, you can do so. There
+ is `git clone` command, but if all you want to do is just to
+ create a copy of your repository (with all the full history that
+ went along with it), you can do so with a regular
+ `cp -a git-tutorial new-git-tutorial`.
++
+Note that when you've moved or copied a git repository, your git index
+file (which caches various information, notably some of the "stat"
+information for the files involved) will likely need to be refreshed.
+So after you do a `cp -a` to create a new copy, you'll want to do
++
+----------------
+$ git-update-index --refresh
+----------------
++
+in the new repository to make sure that the index file is up-to-date.
+
+Note that the second point is true even across machines. You can
+duplicate a remote git repository with *any* regular copy mechanism, be it
+`scp`, `rsync` or `wget`.
+
+When copying a remote repository, you'll want to at a minimum update the
+index cache when you do this, and especially with other peoples'
+repositories you often want to make sure that the index cache is in some
+known state (you don't know *what* they've done and not yet checked in),
+so usually you'll precede the `git-update-index` with a
+
+----------------
+$ git-read-tree --reset HEAD
+$ git-update-index --refresh
+----------------
+
+which will force a total index re-build from the tree pointed to by `HEAD`.
+It resets the index contents to `HEAD`, and then the `git-update-index`
+makes sure to match up all index entries with the checked-out files.
+If the original repository had uncommitted changes in its
+working tree, `git-update-index --refresh` notices them and
+tells you they need to be updated.
+
+The above can also be written as simply
+
+----------------
+$ git reset
+----------------
+
+and in fact a lot of the common git command combinations can be scripted
+with the `git xyz` interfaces. You can learn things by just looking
+at what the various git scripts do. For example, `git reset` is the
+above two lines implemented in `git-reset`, but some things like
+`git status` and `git commit` are slightly more complex scripts around
+the basic git commands.
+
+Many (most?) public remote repositories will not contain any of
+the checked out files or even an index file, and will *only* contain the
+actual core git files. Such a repository usually doesn't even have the
+`.git` subdirectory, but has all the git files directly in the
+repository.
+
+To create your own local live copy of such a "raw" git repository, you'd
+first create your own subdirectory for the project, and then copy the
+raw repository contents into the `.git` directory. For example, to
+create your own copy of the git repository, you'd do the following
+
+----------------
+$ mkdir my-git
+$ cd my-git
+$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
+----------------
+
+followed by
+
+----------------
+$ git-read-tree HEAD
+----------------
+
+to populate the index. However, now you have populated the index, and
+you have all the git internal files, but you will notice that you don't
+actually have any of the working tree files to work on. To get
+those, you'd check them out with
+
+----------------
+$ git-checkout-index -u -a
+----------------
+
+where the `-u` flag means that you want the checkout to keep the index
+up-to-date (so that you don't have to refresh it afterward), and the
+`-a` flag means "check out all files" (if you have a stale copy or an
+older version of a checked out tree you may also need to add the `-f`
+flag first, to tell git-checkout-index to *force* overwriting of any old
+files).
+
+Again, this can all be simplified with
+
+----------------
+$ git clone rsync://rsync.kernel.org/pub/scm/git/git.git/ my-git
+$ cd my-git
+$ git checkout
+----------------
+
+which will end up doing all of the above for you.
+
+You have now successfully copied somebody else's (mine) remote
+repository, and checked it out.
+
+
+Creating a new branch
+---------------------
+
+Branches in git are really nothing more than pointers into the git
+object database from within the `.git/refs/` subdirectory, and as we
+already discussed, the `HEAD` branch is nothing but a symlink to one of
+these object pointers.
+
+You can at any time create a new branch by just picking an arbitrary
+point in the project history, and just writing the SHA1 name of that
+object into a file under `.git/refs/heads/`. You can use any filename you
+want (and indeed, subdirectories), but the convention is that the
+"normal" branch is called `master`. That's just a convention, though,
+and nothing enforces it.
+
+To show that as an example, let's go back to the git-tutorial repository we
+used earlier, and create a branch in it. You do that by simply just
+saying that you want to check out a new branch:
+
+------------
+$ git checkout -b mybranch
+------------
+
+will create a new branch based at the current `HEAD` position, and switch
+to it.
+
+[NOTE]
+================================================
+If you make the decision to start your new branch at some
+other point in the history than the current `HEAD`, you can do so by
+just telling `git checkout` what the base of the checkout would be.
+In other words, if you have an earlier tag or branch, you'd just do
+
+------------
+$ git checkout -b mybranch earlier-commit
+------------
+
+and it would create the new branch `mybranch` at the earlier commit,
+and check out the state at that time.
+================================================
+
+You can always just jump back to your original `master` branch by doing
+
+------------
+$ git checkout master
+------------
+
+(or any other branch-name, for that matter) and if you forget which
+branch you happen to be on, a simple
+
+------------
+$ ls -l .git/HEAD
+------------
+
+will tell you where it's pointing (Note that on platforms with bad or no
+symlink support, you have to execute
+
+------------
+$ cat .git/HEAD
+------------
+
+instead). To get the list of branches you have, you can say
+
+------------
+$ git branch
+------------
+
+which is nothing more than a simple script around `ls .git/refs/heads`.
+There will be asterisk in front of the branch you are currently on.
+
+Sometimes you may wish to create a new branch _without_ actually
+checking it out and switching to it. If so, just use the command
+
+------------
+$ git branch <branchname> [startingpoint]
+------------
+
+which will simply _create_ the branch, but will not do anything further.
+You can then later -- once you decide that you want to actually develop
+on that branch -- switch to that branch with a regular `git checkout`
+with the branchname as the argument.
+
+
+Merging two branches
+--------------------
+
+One of the ideas of having a branch is that you do some (possibly
+experimental) work in it, and eventually merge it back to the main
+branch. So assuming you created the above `mybranch` that started out
+being the same as the original `master` branch, let's make sure we're in
+that branch, and do some work there.
+
+------------------------------------------------
+$ git checkout mybranch
+$ echo "Work, work, work" >>hello
+$ git commit -m 'Some work.' hello
+------------------------------------------------
+
+Here, we just added another line to `hello`, and we used a shorthand for
+doing both `git-update-index hello` and `git commit` by just giving the
+filename directly to `git commit`. The `-m` flag is to give the
+commit log message from the command line.
+
+Now, to make it a bit more interesting, let's assume that somebody else
+does some work in the original branch, and simulate that by going back
+to the master branch, and editing the same file differently there:
+
+------------
+$ git checkout master
+------------
+
+Here, take a moment to look at the contents of `hello`, and notice how they
+don't contain the work we just did in `mybranch` -- because that work
+hasn't happened in the `master` branch at all. Then do
+
+------------
+$ echo "Play, play, play" >>hello
+$ echo "Lots of fun" >>example
+$ git commit -m 'Some fun.' hello example
+------------
+
+since the master branch is obviously in a much better mood.
+
+Now, you've got two branches, and you decide that you want to merge the
+work done. Before we do that, let's introduce a cool graphical tool that
+helps you view what's going on:
+
+----------------
+$ gitk --all
+----------------
+
+will show you graphically both of your branches (that's what the `\--all`
+means: normally it will just show you your current `HEAD`) and their
+histories. You can also see exactly how they came to be from a common
+source.
+
+Anyway, let's exit `gitk` (`^Q` or the File menu), and decide that we want
+to merge the work we did on the `mybranch` branch into the `master`
+branch (which is currently our `HEAD` too). To do that, there's a nice
+script called `git merge`, which wants to know which branches you want
+to resolve and what the merge is all about:
+
+------------
+$ git merge "Merge work in mybranch" HEAD mybranch
+------------
+
+where the first argument is going to be used as the commit message if
+the merge can be resolved automatically.
+
+Now, in this case we've intentionally created a situation where the
+merge will need to be fixed up by hand, though, so git will do as much
+of it as it can automatically (which in this case is just merge the `example`
+file, which had no differences in the `mybranch` branch), and say:
+
+----------------
+ Trying really trivial in-index merge...
+ fatal: Merge requires file-level merging
+ Nope.
+ ...
+ merge: warning: conflicts during merge
+ ERROR: Merge conflict in hello.
+ fatal: merge program failed
+ Automatic merge failed/prevented; fix up by hand
+----------------
+
+which is way too verbose, but it basically tells you that it failed the
+really trivial merge ("Simple merge") and did an "Automatic merge"
+instead, but that too failed due to conflicts in `hello`.
+
+Not to worry. It left the (trivial) conflict in `hello` in the same form you
+should already be well used to if you've ever used CVS, so let's just
+open `hello` in our editor (whatever that may be), and fix it up somehow.
+I'd suggest just making it so that `hello` contains all four lines:
+
+------------
+Hello World
+It's a new day for git
+Play, play, play
+Work, work, work
+------------
+
+and once you're happy with your manual merge, just do a
+
+------------
+$ git commit hello
+------------
+
+which will very loudly warn you that you're now committing a merge
+(which is correct, so never mind), and you can write a small merge
+message about your adventures in git-merge-land.
+
+After you're done, start up `gitk \--all` to see graphically what the
+history looks like. Notice that `mybranch` still exists, and you can
+switch to it, and continue to work with it if you want to. The
+`mybranch` branch will not contain the merge, but next time you merge it
+from the `master` branch, git will know how you merged it, so you'll not
+have to do _that_ merge again.
+
+Another useful tool, especially if you do not always work in X-Window
+environment, is `git show-branch`.
+
+------------------------------------------------
+$ git show-branch master mybranch
+* [master] Merged "mybranch" changes.
+ ! [mybranch] Some work.
+--
++ [master] Merged "mybranch" changes.
+++ [mybranch] Some work.
+------------------------------------------------
+
+The first two lines indicate that it is showing the two branches
+and the first line of the commit log message from their
+top-of-the-tree commits, you are currently on `master` branch
+(notice the asterisk `*` character), and the first column for
+the later output lines is used to show commits contained in the
+`master` branch, and the second column for the `mybranch`
+branch. Three commits are shown along with their log messages.
+All of them have plus `+` characters in the first column, which
+means they are now part of the `master` branch. Only the "Some
+work" commit has the plus `+` character in the second column,
+because `mybranch` has not been merged to incorporate these
+commits from the master branch. The string inside brackets
+before the commit log message is a short name you can use to
+name the commit. In the above example, 'master' and 'mybranch'
+are branch heads. 'master~1' is the first parent of 'master'
+branch head. Please see 'git-rev-parse' documentation if you
+see more complex cases.
+
+Now, let's pretend you are the one who did all the work in
+`mybranch`, and the fruit of your hard work has finally been merged
+to the `master` branch. Let's go back to `mybranch`, and run
+resolve to get the "upstream changes" back to your branch.
+
+------------
+$ git checkout mybranch
+$ git merge "Merge upstream changes." HEAD master
+------------
+
+This outputs something like this (the actual commit object names
+would be different)
+
+----------------
+Updating from ae3a2da... to a80b4aa....
+ example | 1 +
+ hello | 1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+----------------
+
+Because your branch did not contain anything more than what are
+already merged into the `master` branch, the resolve operation did
+not actually do a merge. Instead, it just updated the top of
+the tree of your branch to that of the `master` branch. This is
+often called 'fast forward' merge.
+
+You can run `gitk \--all` again to see how the commit ancestry
+looks like, or run `show-branch`, which tells you this.
+
+------------------------------------------------
+$ git show-branch master mybranch
+! [master] Merged "mybranch" changes.
+ * [mybranch] Merged "mybranch" changes.
+--
+++ [master] Merged "mybranch" changes.
+------------------------------------------------
+
+
+Merging external work
+---------------------
+
+It's usually much more common that you merge with somebody else than
+merging with your own branches, so it's worth pointing out that git
+makes that very easy too, and in fact, it's not that different from
+doing a `git merge`. In fact, a remote merge ends up being nothing
+more than "fetch the work from a remote repository into a temporary tag"
+followed by a `git merge`.
+
+Fetching from a remote repository is done by, unsurprisingly,
+`git fetch`:
+
+----------------
+$ git fetch <remote-repository>
+----------------
+
+One of the following transports can be used to name the
+repository to download from:
+
+Rsync::
+ `rsync://remote.machine/path/to/repo.git/`
++
+Rsync transport is usable for both uploading and downloading,
+but is completely unaware of what git does, and can produce
+unexpected results when you download from the public repository
+while the repository owner is uploading into it via `rsync`
+transport. Most notably, it could update the files under
+`refs/` which holds the object name of the topmost commits
+before uploading the files in `objects/` -- the downloader would
+obtain head commit object name while that object itself is still
+not available in the repository. For this reason, it is
+considered deprecated.
+
+SSH::
+ `remote.machine:/path/to/repo.git/` or
++
+`ssh://remote.machine/path/to/repo.git/`
++
+This transport can be used for both uploading and downloading,
+and requires you to have a log-in privilege over `ssh` to the
+remote machine. It finds out the set of objects the other side
+lacks by exchanging the head commits both ends have and
+transfers (close to) minimum set of objects. It is by far the
+most efficient way to exchange git objects between repositories.
+
+Local directory::
+ `/path/to/repo.git/`
++
+This transport is the same as SSH transport but uses `sh` to run
+both ends on the local machine instead of running other end on
+the remote machine via `ssh`.
+
+git Native::
+ `git://remote.machine/path/to/repo.git/`
++
+This transport was designed for anonymous downloading. Like SSH
+transport, it finds out the set of objects the downstream side
+lacks and transfers (close to) minimum set of objects.
+
+HTTP(S)::
+ `http://remote.machine/path/to/repo.git/`
++
+HTTP and HTTPS transport are used only for downloading. They
+first obtain the topmost commit object name from the remote site
+by looking at `repo.git/info/refs` file, tries to obtain the
+commit object by downloading from `repo.git/objects/xx/xxx\...`
+using the object name of that commit object. Then it reads the
+commit object to find out its parent commits and the associate
+tree object; it repeats this process until it gets all the
+necessary objects. Because of this behaviour, they are
+sometimes also called 'commit walkers'.
++
+The 'commit walkers' are sometimes also called 'dumb
+transports', because they do not require any git aware smart
+server like git Native transport does. Any stock HTTP server
+would suffice.
++
+There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload`
+programs, which are 'commit walkers'; they outlived their
+usefulness when git Native and SSH transports were introduced,
+and not used by `git pull` or `git push` scripts.
+
+Once you fetch from the remote repository, you `resolve` that
+with your current branch.
+
+However -- it's such a common thing to `fetch` and then
+immediately `resolve`, that it's called `git pull`, and you can
+simply do
+
+----------------
+$ git pull <remote-repository>
+----------------
+
+and optionally give a branch-name for the remote end as a second
+argument.
+
+[NOTE]
+You could do without using any branches at all, by
+keeping as many local repositories as you would like to have
+branches, and merging between them with `git pull`, just like
+you merge between branches. The advantage of this approach is
+that it lets you keep set of files for each `branch` checked
+out and you may find it easier to switch back and forth if you
+juggle multiple lines of development simultaneously. Of
+course, you will pay the price of more disk usage to hold
+multiple working trees, but disk space is cheap these days.
+
+[NOTE]
+You could even pull from your own repository by
+giving '.' as <remote-repository> parameter to `git pull`. This
+is useful when you want to merge a local branch (or more, if you
+are making an Octopus) into the current branch.
+
+It is likely that you will be pulling from the same remote
+repository from time to time. As a short hand, you can store
+the remote repository URL in a file under .git/remotes/
+directory, like this:
+
+------------------------------------------------
+$ mkdir -p .git/remotes/
+$ cat >.git/remotes/linus <<\EOF
+URL: http://www.kernel.org/pub/scm/git/git.git/
+EOF
+------------------------------------------------
+
+and use the filename to `git pull` instead of the full URL.
+The URL specified in such file can even be a prefix
+of a full URL, like this:
+
+------------------------------------------------
+$ cat >.git/remotes/jgarzik <<\EOF
+URL: http://www.kernel.org/pub/scm/linux/git/jgarzik/
+EOF
+------------------------------------------------
+
+
+Examples.
+
+. `git pull linus`
+. `git pull linus tag v0.99.1`
+. `git pull jgarzik/netdev-2.6.git/ e100`
+
+the above are equivalent to:
+
+. `git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD`
+. `git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.99.1`
+. `git pull http://www.kernel.org/pub/.../jgarzik/netdev-2.6.git e100`
+
+
+How does the merge work?
+------------------------
+
+We said this tutorial shows what plumbing does to help you cope
+with the porcelain that isn't flushing, but we so far did not
+talk about how the merge really works. If you are following
+this tutorial the first time, I'd suggest to skip to "Publishing
+your work" section and come back here later.
+
+OK, still with me? To give us an example to look at, let's go
+back to the earlier repository with "hello" and "example" file,
+and bring ourselves back to the pre-merge state:
+
+------------
+$ git show-branch --more=3 master mybranch
+! [master] Merge work in mybranch
+ * [mybranch] Merge work in mybranch
+--
+++ [master] Merge work in mybranch
+++ [master^2] Some work.
+++ [master^] Some fun.
+------------
+
+Remember, before running `git merge`, our `master` head was at
+"Some fun." commit, while our `mybranch` head was at "Some
+work." commit.
+
+------------
+$ git checkout mybranch
+$ git reset --hard master^2
+$ git checkout master
+$ git reset --hard master^
+------------
+
+After rewinding, the commit structure should look like this:
+
+------------
+$ git show-branch
+* [master] Some fun.
+ ! [mybranch] Some work.
+--
+ + [mybranch] Some work.
++ [master] Some fun.
+++ [mybranch^] New day.
+------------
+
+Now we are ready to experiment with the merge by hand.
+
+`git merge` command, when merging two branches, uses 3-way merge
+algorithm. First, it finds the common ancestor between them.
+The command it uses is `git-merge-base`:
+
+------------
+$ mb=$(git-merge-base HEAD mybranch)
+------------
+
+The command writes the commit object name of the common ancestor
+to the standard output, so we captured its output to a variable,
+because we will be using it in the next step. BTW, the common
+ancestor commit is the "New day." commit in this case. You can
+tell it by:
+
+------------
+$ git-name-rev $mb
+my-first-tag
+------------
+
+After finding out a common ancestor commit, the second step is
+this:
+
+------------
+$ git-read-tree -m -u $mb HEAD mybranch
+------------
+
+This is the same `git-read-tree` command we have already seen,
+but it takes three trees, unlike previous examples. This reads
+the contents of each tree into different 'stage' in the index
+file (the first tree goes to stage 1, the second stage 2,
+etc.). After reading three trees into three stages, the paths
+that are the same in all three stages are 'collapsed' into stage
+0. Also paths that are the same in two of three stages are
+collapsed into stage 0, taking the SHA1 from either stage 2 or
+stage 3, whichever is different from stage 1 (i.e. only one side
+changed from the common ancestor).
+
+After 'collapsing' operation, paths that are different in three
+trees are left in non-zero stages. At this point, you can
+inspect the index file with this command:
+
+------------
+$ git-ls-files --stage
+100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
+100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
+------------
+
+In our example of only two files, we did not have unchanged
+files so only 'example' resulted in collapsing, but in real-life
+large projects, only small number of files change in one commit,
+and this 'collapsing' tends to trivially merge most of the paths
+fairly quickly, leaving only a handful the real changes in non-zero
+stages.
+
+To look at only non-zero stages, use `\--unmerged` flag:
+
+------------
+$ git-ls-files --unmerged
+100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello
+100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello
+------------
+
+The next step of merging is to merge these three versions of the
+file, using 3-way merge. This is done by giving
+`git-merge-one-file` command as one of the arguments to
+`git-merge-index` command:
+
+------------
+$ git-merge-index git-merge-one-file hello
+Auto-merging hello.
+merge: warning: conflicts during merge
+ERROR: Merge conflict in hello.
+fatal: merge program failed
+------------
+
+`git-merge-one-file` script is called with parameters to
+describe those three versions, and is responsible to leave the
+merge results in the working tree and register it in the index
+file. It is a fairly straightforward shell script, and
+eventually calls `merge` program from RCS suite to perform the
+file-level 3-way merge. In this case, `merge` detects
+conflicts, and the merge result with conflict marks is left in
+the working tree, while the index file is updated with the
+version from the current branch (this is to make `git diff`
+useful after this step). This can be seen if you run `ls-files
+--stage` again at this point:
+
+------------
+$ git-ls-files --stage
+100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 0 hello
+------------
+
+As you can see, there is no unmerged paths in the index file.
+This is the state of the index file and the working file after
+`git merge` returns control back to you, leaving the conflicting
+merge for you to resolve.
+
+
+Publishing your work
+--------------------
+
+So we can use somebody else's work from a remote repository; but
+how can *you* prepare a repository to let other people pull from
+it?
+
+Your do your real work in your working tree that has your
+primary repository hanging under it as its `.git` subdirectory.
+You *could* make that repository accessible remotely and ask
+people to pull from it, but in practice that is not the way
+things are usually done. A recommended way is to have a public
+repository, make it reachable by other people, and when the
+changes you made in your primary working tree are in good shape,
+update the public repository from it. This is often called
+'pushing'.
+
+[NOTE]
+This public repository could further be mirrored, and that is
+how git repositories at `kernel.org` are managed.
+
+Publishing the changes from your local (private) repository to
+your remote (public) repository requires a write privilege on
+the remote machine. You need to have an SSH account there to
+run a single command, `git-receive-pack`.
+
+First, you need to create an empty repository on the remote
+machine that will house your public repository. This empty
+repository will be populated and be kept up-to-date by pushing
+into it later. Obviously, this repository creation needs to be
+done only once.
+
+[NOTE]
+`git push` uses a pair of programs,
+`git-send-pack` on your local machine, and `git-receive-pack`
+on the remote machine. The communication between the two over
+the network internally uses an SSH connection.
+
+Your private repository's git directory is usually `.git`, but
+your public repository is often named after the project name,
+i.e. `<project>.git`. Let's create such a public repository for
+project `my-git`. After logging into the remote machine, create
+an empty directory:
+
+------------
+$ mkdir my-git.git
+------------
+
+Then, make that directory into a git repository by running
+`git init-db`, but this time, since its name is not the usual
+`.git`, we do things slightly differently:
+
+------------
+$ GIT_DIR=my-git.git git-init-db
+------------
+
+Make sure this directory is available for others you want your
+changes to be pulled by via the transport of your choice. Also
+you need to make sure that you have the `git-receive-pack`
+program on the `$PATH`.
+
+[NOTE]
+Many installations of sshd do not invoke your shell as the login
+shell when you directly run programs; what this means is that if
+your login shell is `bash`, only `.bashrc` is read and not
+`.bash_profile`. As a workaround, make sure `.bashrc` sets up
+`$PATH` so that you can run `git-receive-pack` program.
+
+[NOTE]
+If you plan to publish this repository to be accessed over http,
+you should do `chmod +x my-git.git/hooks/post-update` at this
+point. This makes sure that every time you push into this
+repository, `git-update-server-info` is run.
+
+Your "public repository" is now ready to accept your changes.
+Come back to the machine you have your private repository. From
+there, run this command:
+
+------------
+$ git push <public-host>:/path/to/my-git.git master
+------------
+
+This synchronizes your public repository to match the named
+branch head (i.e. `master` in this case) and objects reachable
+from them in your current repository.
+
+As a real example, this is how I update my public git
+repository. Kernel.org mirror network takes care of the
+propagation to other publicly visible machines:
+
+------------
+$ git push master.kernel.org:/pub/scm/git/git.git/
+------------
+
+
+Packing your repository
+-----------------------
+
+Earlier, we saw that one file under `.git/objects/??/` directory
+is stored for each git object you create. This representation
+is efficient to create atomically and safely, but
+not so convenient to transport over the network. Since git objects are
+immutable once they are created, there is a way to optimize the
+storage by "packing them together". The command
+
+------------
+$ git repack
+------------
+
+will do it for you. If you followed the tutorial examples, you
+would have accumulated about 17 objects in `.git/objects/??/`
+directories by now. `git repack` tells you how many objects it
+packed, and stores the packed file in `.git/objects/pack`
+directory.
+
+[NOTE]
+You will see two files, `pack-\*.pack` and `pack-\*.idx`,
+in `.git/objects/pack` directory. They are closely related to
+each other, and if you ever copy them by hand to a different
+repository for whatever reason, you should make sure you copy
+them together. The former holds all the data from the objects
+in the pack, and the latter holds the index for random
+access.
+
+If you are paranoid, running `git-verify-pack` command would
+detect if you have a corrupt pack, but do not worry too much.
+Our programs are always perfect ;-).
+
+Once you have packed objects, you do not need to leave the
+unpacked objects that are contained in the pack file anymore.
+
+------------
+$ git prune-packed
+------------
+
+would remove them for you.
+
+You can try running `find .git/objects -type f` before and after
+you run `git prune-packed` if you are curious. Also `git
+count-objects` would tell you how many unpacked objects are in
+your repository and how much space they are consuming.
+
+[NOTE]
+`git pull` is slightly cumbersome for HTTP transport, as a
+packed repository may contain relatively few objects in a
+relatively large pack. If you expect many HTTP pulls from your
+public repository you might want to repack & prune often, or
+never.
+
+If you run `git repack` again at this point, it will say
+"Nothing to pack". Once you continue your development and
+accumulate the changes, running `git repack` again will create a
+new pack, that contains objects created since you packed your
+repository the last time. We recommend that you pack your project
+soon after the initial import (unless you are starting your
+project from scratch), and then run `git repack` every once in a
+while, depending on how active your project is.
+
+When a repository is synchronized via `git push` and `git pull`
+objects packed in the source repository are usually stored
+unpacked in the destination, unless rsync transport is used.
+While this allows you to use different packing strategies on
+both ends, it also means you may need to repack both
+repositories every once in a while.
+
+
+Working with Others
+-------------------
+
+Although git is a truly distributed system, it is often
+convenient to organize your project with an informal hierarchy
+of developers. Linux kernel development is run this way. There
+is a nice illustration (page 17, "Merges to Mainline") in Randy
+Dunlap's presentation (`http://tinyurl.com/a2jdg`).
+
+It should be stressed that this hierarchy is purely *informal*.
+There is nothing fundamental in git that enforces the "chain of
+patch flow" this hierarchy implies. You do not have to pull
+from only one remote repository.
+
+A recommended workflow for a "project lead" goes like this:
+
+1. Prepare your primary repository on your local machine. Your
+ work is done there.
+
+2. Prepare a public repository accessible to others.
++
+If other people are pulling from your repository over dumb
+transport protocols, you need to keep this repository 'dumb
+transport friendly'. After `git init-db`,
+`$GIT_DIR/hooks/post-update` copied from the standard templates
+would contain a call to `git-update-server-info` but the
+`post-update` hook itself is disabled by default -- enable it
+with `chmod +x post-update`.
+
+3. Push into the public repository from your primary
+ repository.
+
+4. `git repack` the public repository. This establishes a big
+ pack that contains the initial set of objects as the
+ baseline, and possibly `git prune` if the transport
+ used for pulling from your repository supports packed
+ repositories.
+
+5. Keep working in your primary repository. Your changes
+ include modifications of your own, patches you receive via
+ e-mails, and merges resulting from pulling the "public"
+ repositories of your "subsystem maintainers".
++
+You can repack this private repository whenever you feel like.
+
+6. Push your changes to the public repository, and announce it
+ to the public.
+
+7. Every once in a while, "git repack" the public repository.
+ Go back to step 5. and continue working.
+
+
+A recommended work cycle for a "subsystem maintainer" who works
+on that project and has an own "public repository" goes like this:
+
+1. Prepare your work repository, by `git clone` the public
+ repository of the "project lead". The URL used for the
+ initial cloning is stored in `.git/remotes/origin`.
+
+2. Prepare a public repository accessible to others, just like
+ the "project lead" person does.
+
+3. Copy over the packed files from "project lead" public
+ repository to your public repository.
+
+4. Push into the public repository from your primary
+ repository. Run `git repack`, and possibly `git prune` if the
+ transport used for pulling from your repository supports
+ packed repositories.
+
+5. Keep working in your primary repository. Your changes
+ include modifications of your own, patches you receive via
+ e-mails, and merges resulting from pulling the "public"
+ repositories of your "project lead" and possibly your
+ "sub-subsystem maintainers".
++
+You can repack this private repository whenever you feel
+like.
+
+6. Push your changes to your public repository, and ask your
+ "project lead" and possibly your "sub-subsystem
+ maintainers" to pull from it.
+
+7. Every once in a while, `git repack` the public repository.
+ Go back to step 5. and continue working.
+
+
+A recommended work cycle for an "individual developer" who does
+not have a "public" repository is somewhat different. It goes
+like this:
+
+1. Prepare your work repository, by `git clone` the public
+ repository of the "project lead" (or a "subsystem
+ maintainer", if you work on a subsystem). The URL used for
+ the initial cloning is stored in `.git/remotes/origin`.
+
+2. Do your work in your repository on 'master' branch.
+
+3. Run `git fetch origin` from the public repository of your
+ upstream every once in a while. This does only the first
+ half of `git pull` but does not merge. The head of the
+ public repository is stored in `.git/refs/heads/origin`.
+
+4. Use `git cherry origin` to see which ones of your patches
+ were accepted, and/or use `git rebase origin` to port your
+ unmerged changes forward to the updated upstream.
+
+5. Use `git format-patch origin` to prepare patches for e-mail
+ submission to your upstream and send it out. Go back to
+ step 2. and continue.
+
+
+Working with Others, Shared Repository Style
+--------------------------------------------
+
+If you are coming from CVS background, the style of cooperation
+suggested in the previous section may be new to you. You do not
+have to worry. git supports "shared public repository" style of
+cooperation you are probably more familiar with as well.
+
+For this, set up a public repository on a machine that is
+reachable via SSH by people with "commit privileges". Put the
+committers in the same user group and make the repository
+writable by that group.
+
+You, as an individual committer, then:
+
+- First clone the shared repository to a local repository:
+------------------------------------------------
+$ git clone repo.shared.xz:/pub/scm/project.git/ my-project
+$ cd my-project
+$ hack away
+------------------------------------------------
+
+- Merge the work others might have done while you were hacking
+ away:
+------------------------------------------------
+$ git pull origin
+$ test the merge result
+------------------------------------------------
+[NOTE]
+================================
+The first `git clone` would have placed the following in
+`my-project/.git/remotes/origin` file, and that's why this and
+the next step work.
+------------
+URL: repo.shared.xz:/pub/scm/project.git/ my-project
+Pull: master:origin
+------------
+================================
+
+- push your work as the new head of the shared
+ repository.
+------------------------------------------------
+$ git push origin master
+------------------------------------------------
+If somebody else pushed into the same shared repository while
+you were working locally, `git push` in the last step would
+complain, telling you that the remote `master` head does not
+fast forward. You need to pull and merge those other changes
+back before you push your work when it happens.
+
+
+Bundling your work together
+---------------------------
+
+It is likely that you will be working on more than one thing at
+a time. It is easy to use those more-or-less independent tasks
+using branches with git.
+
+We have already seen how branches work in a previous example,
+with "fun and work" example using two branches. The idea is the
+same if there are more than two branches. Let's say you started
+out from "master" head, and have some new code in the "master"
+branch, and two independent fixes in the "commit-fix" and
+"diff-fix" branches:
+
+------------
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+ * [master] Release candidate #1
+---
+ + [diff-fix] Fix rename detection.
+ + [diff-fix~1] Better common substring algorithm.
++ [commit-fix] Fix commit message normalization.
+ + [master] Release candidate #1
++++ [diff-fix~2] Pretty-print messages.
+------------
+
+Both fixes are tested well, and at this point, you want to merge
+in both of them. You could merge in 'diff-fix' first and then
+'commit-fix' next, like this:
+
+------------
+$ git merge 'Merge fix in diff-fix' master diff-fix
+$ git merge 'Merge fix in commit-fix' master commit-fix
+------------
+
+Which would result in:
+
+------------
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+ * [master] Merge fix in commit-fix
+---
+ + [master] Merge fix in commit-fix
++ + [commit-fix] Fix commit message normalization.
+ + [master~1] Merge fix in diff-fix
+ ++ [diff-fix] Fix rename detection.
+ ++ [diff-fix~1] Better common substring algorithm.
+ + [master~2] Release candidate #1
++++ [master~3] Pretty-print messages.
+------------
+
+However, there is no particular reason to merge in one branch
+first and the other next, when what you have are a set of truly
+independent changes (if the order mattered, then they are not
+independent by definition). You could instead merge those two
+branches into the current branch at once. First let's undo what
+we just did and start over. We would want to get the master
+branch before these two merges by resetting it to 'master~2':
+
+------------
+$ git reset --hard master~2
+------------
+
+You can make sure 'git show-branch' matches the state before
+those two 'git merge' you just did. Then, instead of running
+two 'git merge' commands in a row, you would pull these two
+branch heads (this is known as 'making an Octopus'):
+
+------------
+$ git pull . commit-fix diff-fix
+$ git show-branch
+! [commit-fix] Fix commit message normalization.
+ ! [diff-fix] Fix rename detection.
+ * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
+---
+ + [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
++ + [commit-fix] Fix commit message normalization.
+ ++ [diff-fix] Fix rename detection.
+ ++ [diff-fix~1] Better common substring algorithm.
+ + [master~1] Release candidate #1
++++ [master~2] Pretty-print messages.
+------------
+
+Note that you should not do Octopus because you can. An octopus
+is a valid thing to do and often makes it easier to view the
+commit history if you are pulling more than two independent
+changes at the same time. However, if you have merge conflicts
+with any of the branches you are merging in and need to hand
+resolve, that is an indication that the development happened in
+those branches were not independent after all, and you should
+merge two at a time, documenting how you resolved the conflicts,
+and the reason why you preferred changes made in one side over
+the other. Otherwise it would make the project history harder
+to follow, not easier.
+
+[ to be continued.. cvsimports ]
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 000000000..63ccf62ae
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,89 @@
+
+ Git installation
+
+Normally you can just do "make" followed by "make install", and that
+will install the git programs in your own ~/bin/ directory. If you want
+to do a global install, you can do
+
+ $ make prefix=/usr ;# as yourself
+ # make prefix=/usr install ;# as root
+
+(or prefix=/usr/local, of course). Just like any program suite
+that uses $prefix, the built results have some paths encoded,
+which are derived from $prefix, so "make all; make prefix=/usr
+install" would not work.
+
+Issues of note:
+
+ - git normally installs a helper script wrapper called "git", which
+ conflicts with a similarly named "GNU interactive tools" program.
+
+ Tough. Either don't use the wrapper script, or delete the old GNU
+ interactive tools. None of the core git stuff needs the wrapper,
+ it's just a convenient shorthand and while it is documented in some
+ places, you can always replace "git commit" with "git-commit"
+ instead.
+
+ But let's face it, most of us don't have GNU interactive tools, and
+ even if we had it, we wouldn't know what it does. I don't think it
+ has been actively developed since 1997, and people have moved over to
+ graphical file managers.
+
+ - Git is reasonably self-sufficient, but does depend on a few external
+ programs and libraries:
+
+ - "zlib", the compression library. Git won't build without it.
+
+ - "openssl". The git-rev-list program uses bignum support from
+ openssl, and unless you specify otherwise, you'll also get the
+ SHA1 library from here.
+
+ If you don't have openssl, you can use one of the SHA1 libraries
+ that come with git (git includes the one from Mozilla, and has
+ its own PowerPC-optimized one too - see the Makefile), and you
+ can avoid the bignum support by excising git-rev-list support
+ for "--merge-order" (by hand).
+
+ - "libcurl" and "curl" executable. git-http-fetch and
+ git-fetch use them. If you do not use http
+ transfer, you are probabaly OK if you do not have
+ them.
+
+ - expat library; git-http-push uses it for remote lock
+ management over DAV. Similar to "curl" above, this is optional.
+
+ - "GNU diff" to generate patches. Of course, you don't _have_ to
+ generate patches if you don't want to, but let's face it, you'll
+ be wanting to. Or why did you get git in the first place?
+
+ Non-GNU versions of the diff/patch programs don't generally support
+ the unified patch format (which is the one git uses), so you
+ really do want to get the GNU one. Trust me, you will want to
+ do that even if it wasn't for git. There's no point in living
+ in the dark ages any more.
+
+ - "merge", the standard UNIX three-way merge program. It usually
+ comes with the "rcs" package on most Linux distributions, so if
+ you have a developer install you probably have it already, but a
+ "graphical user desktop" install might have left it out.
+
+ You'll only need the merge program if you do development using
+ git, and if you only use git to track other peoples work you'll
+ never notice the lack of it.
+
+ - "wish", the TCL/Tk windowing shell is used in gitk to show the
+ history graphically
+
+ - "ssh" is used to push and pull over the net
+
+ - "perl" and POSIX-compliant shells are needed to use most of
+ the barebone Porcelainish scripts.
+
+ - "python" 2.3 or more recent; if you have 2.3, you may need
+ to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease".
+
+ - Some platform specific issues are dealt with Makefile rules,
+ but depending on your specific installation, you may not
+ have all the libraries/tools needed, or you may have
+ necessary libraries at unusual locations. Please look at the
+ top of the Makefile to see what can be adjusted for your needs.
diff --git a/Makefile b/Makefile
new file mode 100644
index 000000000..d8bb37575
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,482 @@
+# Define MOZILLA_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+# choice) has very fast version optimized for i586.
+#
+# Define NO_OPENSSL environment variable if you do not have OpenSSL. You will
+# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1.
+#
+# Define NO_CURL if you do not have curl installed. git-http-pull and
+# git-http-push are not built, and you cannot use http:// and https://
+# transports.
+#
+# Define CURLDIR=/foo/bar if your curl header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#
+# Define NO_EXPAT if you do not have expat installed. git-http-push is
+# not built, and you cannot push using http:// and https:// transports.
+#
+# Define NO_STRCASESTR if you don't have strcasestr.
+#
+# Define PPC_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine optimized for PowerPC.
+#
+# Define ARM_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine optimized for ARM.
+#
+# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
+#
+# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
+#
+# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
+# Patrick Mauritz).
+#
+# Define NO_MMAP if you want to avoid mmap.
+#
+# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
+#
+# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
+#
+# Define COLLISION_CHECK below if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# sufficient guarantee that no collisions between objects will ever happen.
+
+# Define USE_NSEC below if you want git to care about sub-second file mtimes
+# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
+# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
+# randomly break unless your underlying filesystem supports those sub-second
+# times (my ext3 doesn't).
+
+# Define USE_STDEV below if you want git to care about the underlying device
+# change being considered an inode change from the update-cache perspective.
+
+GIT_VERSION = 0.99.9.GIT
+
+# CFLAGS and LDFLAGS are for the users to override from the command line.
+
+CFLAGS = -g -O2 -Wall
+LDFLAGS =
+ALL_CFLAGS = $(CFLAGS)
+ALL_LDFLAGS = $(LDFLAGS)
+
+prefix = $(HOME)
+bindir = $(prefix)/bin
+template_dir = $(prefix)/share/git-core/templates/
+GIT_PYTHON_DIR = $(prefix)/share/git-core/python
+# DESTDIR=
+
+CC = gcc
+AR = ar
+TAR = tar
+INSTALL = install
+RPMBUILD = rpmbuild
+
+# sparse is architecture-neutral, which means that we need to tell it
+# explicitly what architecture to check for. Fix this up for yours..
+SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
+
+
+
+### --- END CONFIGURATION SECTION ---
+
+SCRIPT_SH = \
+ git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \
+ git-cherry.sh git-clone.sh git-commit.sh \
+ git-count-objects.sh git-diff.sh git-fetch.sh \
+ git-format-patch.sh git-log.sh git-ls-remote.sh \
+ git-merge-one-file.sh git-octopus.sh git-parse-remote.sh \
+ git-prune.sh git-pull.sh git-push.sh git-rebase.sh \
+ git-repack.sh git-request-pull.sh git-reset.sh \
+ git-resolve.sh git-revert.sh git-sh-setup.sh git-status.sh \
+ git-tag.sh git-verify-tag.sh git-whatchanged.sh \
+ git-applymbox.sh git-applypatch.sh git-am.sh \
+ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
+ git-merge-resolve.sh git-merge-ours.sh git-grep.sh \
+ git-lost-found.sh
+
+SCRIPT_PERL = \
+ git-archimport.perl git-cvsimport.perl git-relink.perl \
+ git-shortlog.perl git-fmt-merge-msg.perl \
+ git-svnimport.perl git-mv.perl git-cvsexportcommit.perl
+
+SCRIPT_PYTHON = \
+ git-merge-recursive.py
+
+# The ones that do not have to link with lcrypto nor lz.
+SIMPLE_PROGRAMS = \
+ git-get-tar-commit-id$X git-mailinfo$X git-mailsplit$X \
+ git-stripspace$X git-daemon$X
+
+# ... and all the rest
+PROGRAMS = \
+ git-apply$X git-cat-file$X \
+ git-checkout-index$X git-clone-pack$X git-commit-tree$X \
+ git-convert-objects$X git-diff-files$X \
+ git-diff-index$X git-diff-stages$X \
+ git-diff-tree$X git-fetch-pack$X git-fsck-objects$X \
+ git-hash-object$X git-index-pack$X git-init-db$X \
+ git-local-fetch$X git-ls-files$X git-ls-tree$X git-merge-base$X \
+ git-merge-index$X git-mktag$X git-pack-objects$X git-patch-id$X \
+ git-peek-remote$X git-prune-packed$X git-read-tree$X \
+ git-receive-pack$X git-rev-list$X git-rev-parse$X \
+ git-send-pack$X git-show-branch$X git-shell$X \
+ git-show-index$X git-ssh-fetch$X \
+ git-ssh-upload$X git-tar-tree$X git-unpack-file$X \
+ git-unpack-objects$X git-update-index$X git-update-server-info$X \
+ git-upload-pack$X git-verify-pack$X git-write-tree$X \
+ git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
+ git-name-rev$X git-pack-redundant$X git-var$X $(SIMPLE_PROGRAMS)
+
+# Backward compatibility -- to be removed after 1.0
+PROGRAMS += git-ssh-pull$X git-ssh-push$X
+
+GIT_LIST_TWEAK =
+
+PYMODULES = \
+ gitMergeCommon.py
+
+ifdef WITH_OWN_SUBPROCESS_PY
+ PYMODULES += compat/subprocess.py
+endif
+
+ifdef WITH_SEND_EMAIL
+ SCRIPT_PERL += git-send-email.perl
+else
+ GIT_LIST_TWEAK += -e '/^send-email$$/d'
+endif
+
+LIB_FILE=libgit.a
+
+LIB_H = \
+ blob.h cache.h commit.h count-delta.h csum-file.h delta.h \
+ diff.h epoch.h object.h pack.h pkt-line.h quote.h refs.h \
+ run-command.h strbuf.h tag.h tree.h
+
+DIFF_OBJS = \
+ diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
+ diffcore-pickaxe.o diffcore-rename.o tree-diff.o
+
+LIB_OBJS = \
+ blob.o commit.o connect.o count-delta.o csum-file.o \
+ date.o diff-delta.o entry.o ident.o index.o \
+ object.o pack-check.o patch-delta.o path.o pkt-line.o \
+ quote.o read-cache.o refs.o run-command.o \
+ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
+ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
+ $(DIFF_OBJS)
+
+LIBS = $(LIB_FILE)
+LIBS += -lz
+
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
+#
+# Platform specific tweaks
+#
+
+# We choose to avoid "if .. else if .. else .. endif endif"
+# because maintaining the nesting to match is a pain. If
+# we had "elif" things would have been much nicer...
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
+uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+
+ifeq ($(uname_S),Darwin)
+ NEEDS_SSL_WITH_CRYPTO = YesPlease
+ NEEDS_LIBICONV = YesPlease
+ ## fink
+ ALL_CFLAGS += -I/sw/include
+ ALL_LDFLAGS += -L/sw/lib
+ ## darwinports
+ ALL_CFLAGS += -I/opt/local/include
+ ALL_LDFLAGS += -L/opt/local/lib
+endif
+ifeq ($(uname_S),SunOS)
+ NEEDS_SOCKET = YesPlease
+ NEEDS_NSL = YesPlease
+ NEEDS_LIBICONV = YesPlease
+ SHELL_PATH = /bin/bash
+ NO_STRCASESTR = YesPlease
+ INSTALL = ginstall
+ TAR = gtar
+ ALL_CFLAGS += -D__EXTENSIONS__
+endif
+ifeq ($(uname_O),Cygwin)
+ NO_STRCASESTR = YesPlease
+ NEEDS_LIBICONV = YesPlease
+ # There are conflicting reports about this.
+ # On some boxes NO_MMAP is needed, and not so elsewhere.
+ # Try uncommenting this if you see things break -- YMMV.
+ # NO_MMAP = YesPlease
+ NO_IPV6 = YesPlease
+ X = .exe
+ ALL_CFLAGS += -DUSE_SYMLINK_HEAD=0
+endif
+ifeq ($(uname_S),OpenBSD)
+ NO_STRCASESTR = YesPlease
+ NEEDS_LIBICONV = YesPlease
+ ALL_CFLAGS += -I/usr/local/include
+ ALL_LDFLAGS += -L/usr/local/lib
+endif
+ifeq ($(uname_S),NetBSD)
+ NEEDS_LIBICONV = YesPlease
+ ALL_CFLAGS += -I/usr/pkg/include
+ ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib
+endif
+ifneq (,$(findstring arm,$(uname_M)))
+ ARM_SHA1 = YesPlease
+endif
+
+-include config.mak
+
+ifndef NO_CURL
+ ifdef CURLDIR
+ # This is still problematic -- gcc does not always want -R.
+ ALL_CFLAGS += -I$(CURLDIR)/include
+ CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl
+ else
+ CURL_LIBCURL = -lcurl
+ endif
+ PROGRAMS += git-http-fetch$X
+ ifndef NO_EXPAT
+ EXPAT_LIBEXPAT = -lexpat
+ PROGRAMS += git-http-push$X
+ endif
+endif
+
+ifndef SHELL_PATH
+ SHELL_PATH = /bin/sh
+endif
+ifndef PERL_PATH
+ PERL_PATH = /usr/bin/perl
+endif
+ifndef PYTHON_PATH
+ PYTHON_PATH = /usr/bin/python
+endif
+
+ifndef NO_OPENSSL
+ LIB_OBJS += epoch.o
+ OPENSSL_LIBSSL = -lssl
+ ifdef OPENSSLDIR
+ # Again this may be problematic -- gcc does not always want -R.
+ ALL_CFLAGS += -I$(OPENSSLDIR)/include
+ OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
+ else
+ OPENSSL_LINK =
+ endif
+else
+ ALL_CFLAGS += -DNO_OPENSSL
+ MOZILLA_SHA1 = 1
+ OPENSSL_LIBSSL =
+endif
+ifdef NEEDS_SSL_WITH_CRYPTO
+ LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto -lssl
+else
+ LIB_4_CRYPTO = $(OPENSSL_LINK) -lcrypto
+endif
+ifdef NEEDS_LIBICONV
+ ifdef ICONVDIR
+ # Again this may be problematic -- gcc does not always want -R.
+ ALL_CFLAGS += -I$(ICONVDIR)/include
+ ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
+ else
+ ICONV_LINK =
+ endif
+ LIB_4_ICONV = $(ICONV_LINK) -liconv
+else
+ LIB_4_ICONV =
+endif
+ifdef NEEDS_SOCKET
+ LIBS += -lsocket
+ SIMPLE_LIB += -lsocket
+endif
+ifdef NEEDS_NSL
+ LIBS += -lnsl
+ SIMPLE_LIB += -lnsl
+endif
+ifdef NO_STRCASESTR
+ ALL_CFLAGS += -Dstrcasestr=gitstrcasestr -DNO_STRCASESTR=1
+ LIB_OBJS += compat/strcasestr.o
+endif
+ifdef NO_MMAP
+ ALL_CFLAGS += -Dmmap=gitfakemmap -Dmunmap=gitfakemunmap -DNO_MMAP
+ LIB_OBJS += compat/mmap.o
+endif
+ifdef NO_IPV6
+ ALL_CFLAGS += -DNO_IPV6 -Dsockaddr_storage=sockaddr_in
+endif
+
+ifdef PPC_SHA1
+ SHA1_HEADER = "ppc/sha1.h"
+ LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o
+else
+ifdef ARM_SHA1
+ SHA1_HEADER = "arm/sha1.h"
+ LIB_OBJS += arm/sha1.o arm/sha1_arm.o
+else
+ifdef MOZILLA_SHA1
+ SHA1_HEADER = "mozilla-sha1/sha1.h"
+ LIB_OBJS += mozilla-sha1/sha1.o
+else
+ SHA1_HEADER = <openssl/sha.h>
+ LIBS += $(LIB_4_CRYPTO)
+endif
+endif
+endif
+
+ALL_CFLAGS += -DSHA1_HEADER=$(call shellquote,$(SHA1_HEADER))
+
+SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
+ $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+ $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
+ gitk git-cherry-pick
+
+export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
+### Build rules
+
+all: $(PROGRAMS) $(SCRIPTS) git
+
+all:
+ $(MAKE) -C templates
+
+# Only use $(CFLAGS). We don't need anything else.
+git: git.c Makefile
+ $(CC) -DGIT_EXEC_PATH='"$(bindir)"' -DGIT_VERSION='"$(GIT_VERSION)"' \
+ $(CFLAGS) $@.c -o $@
+
+$(filter-out git,$(patsubst %.sh,%,$(SCRIPT_SH))) : % : %.sh
+ rm -f $@
+ sed -e '1s|#!.*/sh|#!$(call shq,$(SHELL_PATH))|' \
+ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+ $@.sh >$@
+ chmod +x $@
+
+$(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
+ rm -f $@
+ sed -e '1s|#!.*perl|#!$(call shq,$(PERL_PATH))|' \
+ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+ $@.perl >$@
+ chmod +x $@
+
+$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
+ rm -f $@
+ sed -e '1s|#!.*python|#!$(call shq,$(PYTHON_PATH))|' \
+ -e 's|@@GIT_PYTHON_PATH@@|$(call shq,$(GIT_PYTHON_DIR))|g' \
+ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
+ $@.py >$@
+ chmod +x $@
+
+git-cherry-pick: git-revert
+ cp $< $@
+
+%.o: %.c
+ $(CC) -o $*.o -c $(ALL_CFLAGS) $<
+%.o: %.S
+ $(CC) -o $*.o -c $(ALL_CFLAGS) $<
+
+git-%$X: %.o $(LIB_FILE)
+ $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+
+git-mailinfo$X : SIMPLE_LIB += $(LIB_4_ICONV)
+$(SIMPLE_PROGRAMS) : $(LIB_FILE)
+$(SIMPLE_PROGRAMS) : git-%$X : %.o
+ $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+ $(LIB_FILE) $(SIMPLE_LIB)
+
+git-http-fetch$X: fetch.o
+git-local-fetch$X: fetch.o
+git-ssh-fetch$X: rsh.o fetch.o
+git-ssh-upload$X: rsh.o
+git-ssh-pull$X: rsh.o fetch.o
+git-ssh-push$X: rsh.o
+
+git-http-fetch$X: LIBS += $(CURL_LIBCURL)
+git-http-push$X: LIBS += $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
+git-rev-list$X: LIBS += $(OPENSSL_LIBSSL)
+
+init-db.o: init-db.c
+ $(CC) -c $(ALL_CFLAGS) \
+ -DDEFAULT_GIT_TEMPLATE_DIR=$(call shellquote,"$(template_dir)") $*.c
+
+$(LIB_OBJS): $(LIB_H)
+$(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H)
+$(DIFF_OBJS): diffcore.h
+
+$(LIB_FILE): $(LIB_OBJS)
+ $(AR) rcs $@ $(LIB_OBJS)
+
+doc:
+ $(MAKE) -C Documentation all
+
+
+### Testing rules
+
+test: all
+ $(MAKE) -C t/ all
+
+test-date$X: test-date.c date.o ctype.o
+ $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
+
+test-delta$X: test-delta.c diff-delta.o patch-delta.o
+ $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
+
+check:
+ for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i; done
+
+
+
+### Installation rules
+
+install: $(PROGRAMS) $(SCRIPTS) git
+ $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(bindir))
+ $(INSTALL) git $(PROGRAMS) $(SCRIPTS) $(call shellquote,$(DESTDIR)$(bindir))
+ $(MAKE) -C templates install
+ $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
+ $(INSTALL) $(PYMODULES) $(call shellquote,$(DESTDIR)$(GIT_PYTHON_DIR))
+
+install-doc:
+ $(MAKE) -C Documentation install
+
+
+
+
+### Maintainer's dist rules
+
+git.spec: git.spec.in Makefile
+ sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@
+
+GIT_TARNAME=git-$(GIT_VERSION)
+dist: git.spec git-tar-tree
+ ./git-tar-tree HEAD $(GIT_TARNAME) > $(GIT_TARNAME).tar
+ @mkdir -p $(GIT_TARNAME)
+ @cp git.spec $(GIT_TARNAME)
+ $(TAR) rf $(GIT_TARNAME).tar $(GIT_TARNAME)/git.spec
+ @rm -rf $(GIT_TARNAME)
+ gzip -f -9 $(GIT_TARNAME).tar
+
+rpm: dist
+ $(RPMBUILD) -ta $(GIT_TARNAME).tar.gz
+
+deb: dist
+ rm -rf $(GIT_TARNAME)
+ $(TAR) zxf $(GIT_TARNAME).tar.gz
+ dpkg-source -b $(GIT_TARNAME)
+ cd $(GIT_TARNAME) && fakeroot debian/rules binary
+
+### Cleaning rules
+
+clean:
+ rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o git $(PROGRAMS) $(LIB_FILE)
+ rm -f $(filter-out gitk,$(SCRIPTS))
+ rm -f *.spec *.pyc *.pyo
+ rm -rf $(GIT_TARNAME)
+ rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
+ rm -f git-core_$(GIT_VERSION)-*.dsc
+ rm -f git-*_$(GIT_VERSION)-*.deb
+ $(MAKE) -C Documentation/ clean
+ $(MAKE) -C templates clean
+ $(MAKE) -C t/ clean
diff --git a/README b/README
new file mode 100644
index 000000000..36fef6ec0
--- /dev/null
+++ b/README
@@ -0,0 +1,586 @@
+////////////////////////////////////////////////////////////////
+
+ GIT - the stupid content tracker
+
+////////////////////////////////////////////////////////////////
+"git" can mean anything, depending on your mood.
+
+ - random three-letter combination that is pronounceable, and not
+ actually used by any common UNIX command. The fact that it is a
+ mispronunciation of "get" may or may not be relevant.
+ - stupid. contemptible and despicable. simple. Take your pick from the
+ dictionary of slang.
+ - "global information tracker": you're in a good mood, and it actually
+ works for you. Angels sing, and a light suddenly fills the room.
+ - "goddamn idiotic truckload of sh*t": when it breaks
+
+This is a stupid (but extremely fast) directory content manager. It
+doesn't do a whole lot, but what it 'does' do is track directory
+contents efficiently.
+
+There are two object abstractions: the "object database", and the
+"current directory cache" aka "index".
+
+The Object Database
+~~~~~~~~~~~~~~~~~~~
+The object database is literally just a content-addressable collection
+of objects. All objects are named by their content, which is
+approximated by the SHA1 hash of the object itself. Objects may refer
+to other objects (by referencing their SHA1 hash), and so you can
+build up a hierarchy of objects.
+
+All objects have a statically determined "type" aka "tag", which is
+determined at object creation time, and which identifies the format of
+the object (i.e. how it is used, and how it can refer to other
+objects). There are currently four different object types: "blob",
+"tree", "commit" and "tag".
+
+A "blob" object cannot refer to any other object, and is, like the tag
+implies, a pure storage object containing some user data. It is used to
+actually store the file data, i.e. a blob object is associated with some
+particular version of some file.
+
+A "tree" object is an object that ties one or more "blob" objects into a
+directory structure. In addition, a tree object can refer to other tree
+objects, thus creating a directory hierarchy.
+
+A "commit" object ties such directory hierarchies together into
+a DAG of revisions - each "commit" is associated with exactly one tree
+(the directory hierarchy at the time of the commit). In addition, a
+"commit" refers to one or more "parent" commit objects that describe the
+history of how we arrived at that directory hierarchy.
+
+As a special case, a commit object with no parents is called the "root"
+object, and is the point of an initial project commit. Each project
+must have at least one root, and while you can tie several different
+root objects together into one project by creating a commit object which
+has two or more separate roots as its ultimate parents, that's probably
+just going to confuse people. So aim for the notion of "one root object
+per project", even if git itself does not enforce that.
+
+A "tag" object symbolically identifies and can be used to sign other
+objects. It contains the identifier and type of another object, a
+symbolic name (of course!) and, optionally, a signature.
+
+Regardless of object type, all objects share the following
+characteristics: they are all deflated with zlib, and have a header
+that not only specifies their tag, but also provides size information
+about the data in the object. It's worth noting that the SHA1 hash
+that is used to name the object is the hash of the original data
+plus this header, so `sha1sum` 'file' does not match the object name
+for 'file'.
+(Historical note: in the dawn of the age of git the hash
+was the sha1 of the 'compressed' object.)
+
+As a result, the general consistency of an object can always be tested
+independently of the contents or the type of the object: all objects can
+be validated by verifying that (a) their hashes match the content of the
+file and (b) the object successfully inflates to a stream of bytes that
+forms a sequence of <ascii tag without space> + <space> + <ascii decimal
+size> + <byte\0> + <binary object data>.
+
+The structured objects can further have their structure and
+connectivity to other objects verified. This is generally done with
+the `git-fsck-objects` program, which generates a full dependency graph
+of all objects, and verifies their internal consistency (in addition
+to just verifying their superficial consistency through the hash).
+
+The object types in some more detail:
+
+Blob Object
+~~~~~~~~~~~
+A "blob" object is nothing but a binary blob of data, and doesn't
+refer to anything else. There is no signature or any other
+verification of the data, so while the object is consistent (it 'is'
+indexed by its sha1 hash, so the data itself is certainly correct), it
+has absolutely no other attributes. No name associations, no
+permissions. It is purely a blob of data (i.e. normally "file
+contents").
+
+In particular, since the blob is entirely defined by its data, if two
+files in a directory tree (or in multiple different versions of the
+repository) have the same contents, they will share the same blob
+object. The object is totally independent of its location in the
+directory tree, and renaming a file does not change the object that
+file is associated with in any way.
+
+A blob is typically created when gitlink:git-update-index[1]
+is run, and its data can be accessed by gitlink:git-cat-file[1].
+
+Tree Object
+~~~~~~~~~~~
+The next hierarchical object type is the "tree" object. A tree object
+is a list of mode/name/blob data, sorted by name. Alternatively, the
+mode data may specify a directory mode, in which case instead of
+naming a blob, that name is associated with another TREE object.
+
+Like the "blob" object, a tree object is uniquely determined by the
+set contents, and so two separate but identical trees will always
+share the exact same object. This is true at all levels, i.e. it's
+true for a "leaf" tree (which does not refer to any other trees, only
+blobs) as well as for a whole subdirectory.
+
+For that reason a "tree" object is just a pure data abstraction: it
+has no history, no signatures, no verification of validity, except
+that since the contents are again protected by the hash itself, we can
+trust that the tree is immutable and its contents never change.
+
+So you can trust the contents of a tree to be valid, the same way you
+can trust the contents of a blob, but you don't know where those
+contents 'came' from.
+
+Side note on trees: since a "tree" object is a sorted list of
+"filename+content", you can create a diff between two trees without
+actually having to unpack two trees. Just ignore all common parts,
+and your diff will look right. In other words, you can effectively
+(and efficiently) tell the difference between any two random trees by
+O(n) where "n" is the size of the difference, rather than the size of
+the tree.
+
+Side note 2 on trees: since the name of a "blob" depends entirely and
+exclusively on its contents (i.e. there are no names or permissions
+involved), you can see trivial renames or permission changes by
+noticing that the blob stayed the same. However, renames with data
+changes need a smarter "diff" implementation.
+
+A tree is created with gitlink:git-write-tree[1] and
+its data can be accessed by gitlink:git-ls-tree[1].
+Two trees can be compared with gitlink:git-diff-tree[1].
+
+Commit Object
+~~~~~~~~~~~~~
+The "commit" object is an object that introduces the notion of
+history into the picture. In contrast to the other objects, it
+doesn't just describe the physical state of a tree, it describes how
+we got there, and why.
+
+A "commit" is defined by the tree-object that it results in, the
+parent commits (zero, one or more) that led up to that point, and a
+comment on what happened. Again, a commit is not trusted per se:
+the contents are well-defined and "safe" due to the cryptographically
+strong signatures at all levels, but there is no reason to believe
+that the tree is "good" or that the merge information makes sense.
+The parents do not have to actually have any relationship with the
+result, for example.
+
+Note on commits: unlike real SCM's, commits do not contain
+rename information or file mode change information. All of that is
+implicit in the trees involved (the result tree, and the result trees
+of the parents), and describing that makes no sense in this idiotic
+file manager.
+
+A commit is created with gitlink:git-commit-tree[1] and
+its data can be accessed by gitlink:git-cat-file[1].
+
+Trust
+~~~~~
+An aside on the notion of "trust". Trust is really outside the scope
+of "git", but it's worth noting a few things. First off, since
+everything is hashed with SHA1, you 'can' trust that an object is
+intact and has not been messed with by external sources. So the name
+of an object uniquely identifies a known state - just not a state that
+you may want to trust.
+
+Furthermore, since the SHA1 signature of a commit refers to the
+SHA1 signatures of the tree it is associated with and the signatures
+of the parent, a single named commit specifies uniquely a whole set
+of history, with full contents. You can't later fake any step of the
+way once you have the name of a commit.
+
+So to introduce some real trust in the system, the only thing you need
+to do is to digitally sign just 'one' special note, which includes the
+name of a top-level commit. Your digital signature shows others
+that you trust that commit, and the immutability of the history of
+commits tells others that they can trust the whole history.
+
+In other words, you can easily validate a whole archive by just
+sending out a single email that tells the people the name (SHA1 hash)
+of the top commit, and digitally sign that email using something
+like GPG/PGP.
+
+To assist in this, git also provides the tag object...
+
+Tag Object
+~~~~~~~~~~
+Git provides the "tag" object to simplify creating, managing and
+exchanging symbolic and signed tokens. The "tag" object at its
+simplest simply symbolically identifies another object by containing
+the sha1, type and symbolic name.
+
+However it can optionally contain additional signature information
+(which git doesn't care about as long as there's less than 8k of
+it). This can then be verified externally to git.
+
+Note that despite the tag features, "git" itself only handles content
+integrity; the trust framework (and signature provision and
+verification) has to come from outside.
+
+A tag is created with gitlink:git-mktag[1],
+its data can be accessed by gitlink:git-cat-file[1],
+and the signature can be verified by
+gitlink:git-verify-tag[1].
+
+
+The "index" aka "Current Directory Cache"
+-----------------------------------------
+The index is a simple binary file, which contains an efficient
+representation of a virtual directory content at some random time. It
+does so by a simple array that associates a set of names, dates,
+permissions and content (aka "blob") objects together. The cache is
+always kept ordered by name, and names are unique (with a few very
+specific rules) at any point in time, but the cache has no long-term
+meaning, and can be partially updated at any time.
+
+In particular, the index certainly does not need to be consistent with
+the current directory contents (in fact, most operations will depend on
+different ways to make the index 'not' be consistent with the directory
+hierarchy), but it has three very important attributes:
+
+'(a) it can re-generate the full state it caches (not just the
+directory structure: it contains pointers to the "blob" objects so
+that it can regenerate the data too)'
+
+As a special case, there is a clear and unambiguous one-way mapping
+from a current directory cache to a "tree object", which can be
+efficiently created from just the current directory cache without
+actually looking at any other data. So a directory cache at any one
+time uniquely specifies one and only one "tree" object (but has
+additional data to make it easy to match up that tree object with what
+has happened in the directory)
+
+'(b) it has efficient methods for finding inconsistencies between that
+cached state ("tree object waiting to be instantiated") and the
+current state.'
+
+'(c) it can additionally efficiently represent information about merge
+conflicts between different tree objects, allowing each pathname to be
+associated with sufficient information about the trees involved that
+you can create a three-way merge between them.'
+
+Those are the three ONLY things that the directory cache does. It's a
+cache, and the normal operation is to re-generate it completely from a
+known tree object, or update/compare it with a live tree that is being
+developed. If you blow the directory cache away entirely, you generally
+haven't lost any information as long as you have the name of the tree
+that it described.
+
+At the same time, the index is at the same time also the
+staging area for creating new trees, and creating a new tree always
+involves a controlled modification of the index file. In particular,
+the index file can have the representation of an intermediate tree that
+has not yet been instantiated. So the index can be thought of as a
+write-back cache, which can contain dirty information that has not yet
+been written back to the backing store.
+
+
+
+The Workflow
+------------
+Generally, all "git" operations work on the index file. Some operations
+work *purely* on the index file (showing the current state of the
+index), but most operations move data to and from the index file. Either
+from the database or from the working directory. Thus there are four
+main combinations:
+
+1) working directory -> index
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You update the index with information from the working directory with
+the gitlink:git-update-index[1] command. You
+generally update the index information by just specifying the filename
+you want to update, like so:
+
+ git-update-index filename
+
+but to avoid common mistakes with filename globbing etc, the command
+will not normally add totally new entries or remove old entries,
+i.e. it will normally just update existing cache entries.
+
+To tell git that yes, you really do realize that certain files no
+longer exist in the archive, or that new files should be added, you
+should use the `--remove` and `--add` flags respectively.
+
+NOTE! A `--remove` flag does 'not' mean that subsequent filenames will
+necessarily be removed: if the files still exist in your directory
+structure, the index will be updated with their new status, not
+removed. The only thing `--remove` means is that update-cache will be
+considering a removed file to be a valid thing, and if the file really
+does not exist any more, it will update the index accordingly.
+
+As a special case, you can also do `git-update-index --refresh`, which
+will refresh the "stat" information of each index to match the current
+stat information. It will 'not' update the object status itself, and
+it will only update the fields that are used to quickly test whether
+an object still matches its old backing store object.
+
+2) index -> object database
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You write your current index file to a "tree" object with the program
+
+ git-write-tree
+
+that doesn't come with any options - it will just write out the
+current index into the set of tree objects that describe that state,
+and it will return the name of the resulting top-level tree. You can
+use that tree to re-generate the index at any time by going in the
+other direction:
+
+3) object database -> index
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You read a "tree" file from the object database, and use that to
+populate (and overwrite - don't do this if your index contains any
+unsaved state that you might want to restore later!) your current
+index. Normal operation is just
+
+ git-read-tree <sha1 of tree>
+
+and your index file will now be equivalent to the tree that you saved
+earlier. However, that is only your 'index' file: your working
+directory contents have not been modified.
+
+4) index -> working directory
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You update your working directory from the index by "checking out"
+files. This is not a very common operation, since normally you'd just
+keep your files updated, and rather than write to your working
+directory, you'd tell the index files about the changes in your
+working directory (i.e. `git-update-index`).
+
+However, if you decide to jump to a new version, or check out somebody
+else's version, or just restore a previous tree, you'd populate your
+index file with read-tree, and then you need to check out the result
+with
+
+ git-checkout-index filename
+
+or, if you want to check out all of the index, use `-a`.
+
+NOTE! git-checkout-index normally refuses to overwrite old files, so
+if you have an old version of the tree already checked out, you will
+need to use the "-f" flag ('before' the "-a" flag or the filename) to
+'force' the checkout.
+
+
+Finally, there are a few odds and ends which are not purely moving
+from one representation to the other:
+
+5) Tying it all together
+~~~~~~~~~~~~~~~~~~~~~~~~
+To commit a tree you have instantiated with "git-write-tree", you'd
+create a "commit" object that refers to that tree and the history
+behind it - most notably the "parent" commits that preceded it in
+history.
+
+Normally a "commit" has one parent: the previous state of the tree
+before a certain change was made. However, sometimes it can have two
+or more parent commits, in which case we call it a "merge", due to the
+fact that such a commit brings together ("merges") two or more
+previous states represented by other commits.
+
+In other words, while a "tree" represents a particular directory state
+of a working directory, a "commit" represents that state in "time",
+and explains how we got there.
+
+You create a commit object by giving it the tree that describes the
+state at the time of the commit, and a list of parents:
+
+ git-commit-tree <tree> -p <parent> [-p <parent2> ..]
+
+and then giving the reason for the commit on stdin (either through
+redirection from a pipe or file, or by just typing it at the tty).
+
+git-commit-tree will return the name of the object that represents
+that commit, and you should save it away for later use. Normally,
+you'd commit a new `HEAD` state, and while git doesn't care where you
+save the note about that state, in practice we tend to just write the
+result to the file pointed at by `.git/HEAD`, so that we can always see
+what the last committed state was.
+
+Here is an ASCII art by Jon Loeliger that illustrates how
+various pieces fit together.
+
+------------
+
+ commit-tree
+ commit obj
+ +----+
+ | |
+ | |
+ V V
+ +-----------+
+ | Object DB |
+ | Backing |
+ | Store |
+ +-----------+
+ ^
+ write-tree | |
+ tree obj | |
+ | | read-tree
+ | | tree obj
+ V
+ +-----------+
+ | Index |
+ | "cache" |
+ +-----------+
+ update-index ^
+ blob obj | |
+ | |
+ checkout-index -u | | checkout-index
+ stat | | blob obj
+ V
+ +-----------+
+ | Working |
+ | Directory |
+ +-----------+
+
+------------
+
+
+6) Examining the data
+~~~~~~~~~~~~~~~~~~~~~
+
+You can examine the data represented in the object database and the
+index with various helper tools. For every object, you can use
+gitlink:git-cat-file[1] to examine details about the
+object:
+
+ git-cat-file -t <objectname>
+
+shows the type of the object, and once you have the type (which is
+usually implicit in where you find the object), you can use
+
+ git-cat-file blob|tree|commit|tag <objectname>
+
+to show its contents. NOTE! Trees have binary content, and as a result
+there is a special helper for showing that content, called
+`git-ls-tree`, which turns the binary content into a more easily
+readable form.
+
+It's especially instructive to look at "commit" objects, since those
+tend to be small and fairly self-explanatory. In particular, if you
+follow the convention of having the top commit name in `.git/HEAD`,
+you can do
+
+ git-cat-file commit HEAD
+
+to see what the top commit was.
+
+7) Merging multiple trees
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Git helps you do a three-way merge, which you can expand to n-way by
+repeating the merge procedure arbitrary times until you finally
+"commit" the state. The normal situation is that you'd only do one
+three-way merge (two parents), and commit it, but if you like to, you
+can do multiple parents in one go.
+
+To do a three-way merge, you need the two sets of "commit" objects
+that you want to merge, use those to find the closest common parent (a
+third "commit" object), and then use those commit objects to find the
+state of the directory ("tree" object) at these points.
+
+To get the "base" for the merge, you first look up the common parent
+of two commits with
+
+ git-merge-base <commit1> <commit2>
+
+which will return you the commit they are both based on. You should
+now look up the "tree" objects of those commits, which you can easily
+do with (for example)
+
+ git-cat-file commit <commitname> | head -1
+
+since the tree object information is always the first line in a commit
+object.
+
+Once you know the three trees you are going to merge (the one
+"original" tree, aka the common case, and the two "result" trees, aka
+the branches you want to merge), you do a "merge" read into the
+index. This will complain if it has to throw away your old index contents, so you should
+make sure that you've committed those - in fact you would normally
+always do a merge against your last commit (which should thus match
+what you have in your current index anyway).
+
+To do the merge, do
+
+ git-read-tree -m -u <origtree> <yourtree> <targettree>
+
+which will do all trivial merge operations for you directly in the
+index file, and you can just write the result out with
+`git-write-tree`.
+
+Historical note. We did not have `-u` facility when this
+section was first written, so we used to warn that
+the merge is done in the index file, not in your
+working directory, and your working directory will no longer match your
+index.
+
+
+8) Merging multiple trees, continued
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sadly, many merges aren't trivial. If there are files that have
+been added.moved or removed, or if both branches have modified the
+same file, you will be left with an index tree that contains "merge
+entries" in it. Such an index tree can 'NOT' be written out to a tree
+object, and you will have to resolve any such merge clashes using
+other tools before you can write out the result.
+
+You can examine such index state with `git-ls-files --unmerged`
+command. An example:
+
+------------------------------------------------
+$ git-read-tree -m $orig HEAD $target
+$ git-ls-files --unmerged
+100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello.c
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello.c
+100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello.c
+------------------------------------------------
+
+Each line of the `git-ls-files --unmerged` output begins with
+the blob mode bits, blob SHA1, 'stage number', and the
+filename. The 'stage number' is git's way to say which tree it
+came from: stage 1 corresponds to `$orig` tree, stage 2 `HEAD`
+tree, and stage3 `$target` tree.
+
+Earlier we said that trivial merges are done inside
+`git-read-tree -m`. For example, if the file did not change
+from `$orig` to `HEAD` nor `$target`, or if the file changed
+from `$orig` to `HEAD` and `$orig` to `$target` the same way,
+obviously the final outcome is what is in `HEAD`. What the
+above example shows is that file `hello.c` was changed from
+`$orig` to `HEAD` and `$orig` to `$target` in a different way.
+You could resolve this by running your favorite 3-way merge
+program, e.g. `diff3` or `merge`, on the blob objects from
+these three stages yourself, like this:
+
+------------------------------------------------
+$ git-cat-file blob 263414f... >hello.c~1
+$ git-cat-file blob 06fa6a2... >hello.c~2
+$ git-cat-file blob cc44c73... >hello.c~3
+$ merge hello.c~2 hello.c~1 hello.c~3
+------------------------------------------------
+
+This would leave the merge result in `hello.c~2` file, along
+with conflict markers if there are conflicts. After verifying
+the merge result makes sense, you can tell git what the final
+merge result for this file is by:
+
+ mv -f hello.c~2 hello.c
+ git-update-index hello.c
+
+When a path is in unmerged state, running `git-update-index` for
+that path tells git to mark the path resolved.
+
+The above is the description of a git merge at the lowest level,
+to help you understand what conceptually happens under the hood.
+In practice, nobody, not even git itself, uses three `git-cat-file`
+for this. There is `git-merge-index` program that extracts the
+stages to temporary files and calls a `merge` script on it
+
+ git-merge-index git-merge-one-file hello.c
+
+and that is what higher level `git resolve` is implemented with.
diff --git a/apply.c b/apply.c
new file mode 100644
index 000000000..50be8f3e2
--- /dev/null
+++ b/apply.c
@@ -0,0 +1,1858 @@
+/*
+ * apply.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * This applies patches on top of some (arbitrary) version of the SCM.
+ *
+ */
+#include <fnmatch.h>
+#include "cache.h"
+#include "quote.h"
+
+// --check turns on checking that the working tree matches the
+// files that are being modified, but doesn't apply the patch
+// --stat does just a diffstat, and doesn't actually apply
+// --numstat does numeric diffstat, and doesn't actually apply
+// --index-info shows the old and new index info for paths if available.
+//
+static int allow_binary_replacement = 0;
+static int check_index = 0;
+static int write_index = 0;
+static int diffstat = 0;
+static int numstat = 0;
+static int summary = 0;
+static int check = 0;
+static int apply = 1;
+static int no_add = 0;
+static int show_index_info = 0;
+static int line_termination = '\n';
+static const char apply_usage[] =
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] <patch>...";
+
+/*
+ * For "diff-stat" like behaviour, we keep track of the biggest change
+ * we've seen, and the longest filename. That allows us to do simple
+ * scaling.
+ */
+static int max_change, max_len;
+
+/*
+ * Various "current state", notably line numbers and what
+ * file (and how) we're patching right now.. The "is_xxxx"
+ * things are flags, where -1 means "don't know yet".
+ */
+static int linenr = 1;
+
+struct fragment {
+ unsigned long oldpos, oldlines;
+ unsigned long newpos, newlines;
+ const char *patch;
+ int size;
+ struct fragment *next;
+};
+
+struct patch {
+ char *new_name, *old_name, *def_name;
+ unsigned int old_mode, new_mode;
+ int is_rename, is_copy, is_new, is_delete, is_binary;
+ int lines_added, lines_deleted;
+ int score;
+ struct fragment *fragments;
+ char *result;
+ unsigned long resultsize;
+ char old_sha1_prefix[41];
+ char new_sha1_prefix[41];
+ struct patch *next;
+};
+
+#define CHUNKSIZE (8192)
+#define SLOP (16)
+
+static void *read_patch_file(int fd, unsigned long *sizep)
+{
+ unsigned long size = 0, alloc = CHUNKSIZE;
+ void *buffer = xmalloc(alloc);
+
+ for (;;) {
+ int nr = alloc - size;
+ if (nr < 1024) {
+ alloc += CHUNKSIZE;
+ buffer = xrealloc(buffer, alloc);
+ nr = alloc - size;
+ }
+ nr = read(fd, buffer + size, nr);
+ if (!nr)
+ break;
+ if (nr < 0) {
+ if (errno == EAGAIN)
+ continue;
+ die("git-apply: read returned %s", strerror(errno));
+ }
+ size += nr;
+ }
+ *sizep = size;
+
+ /*
+ * Make sure that we have some slop in the buffer
+ * so that we can do speculative "memcmp" etc, and
+ * see to it that it is NUL-filled.
+ */
+ if (alloc < size + SLOP)
+ buffer = xrealloc(buffer, size + SLOP);
+ memset(buffer + size, 0, SLOP);
+ return buffer;
+}
+
+static unsigned long linelen(const char *buffer, unsigned long size)
+{
+ unsigned long len = 0;
+ while (size--) {
+ len++;
+ if (*buffer++ == '\n')
+ break;
+ }
+ return len;
+}
+
+static int is_dev_null(const char *str)
+{
+ return !memcmp("/dev/null", str, 9) && isspace(str[9]);
+}
+
+#define TERM_SPACE 1
+#define TERM_TAB 2
+
+static int name_terminate(const char *name, int namelen, int c, int terminate)
+{
+ if (c == ' ' && !(terminate & TERM_SPACE))
+ return 0;
+ if (c == '\t' && !(terminate & TERM_TAB))
+ return 0;
+
+ return 1;
+}
+
+static char * find_name(const char *line, char *def, int p_value, int terminate)
+{
+ int len;
+ const char *start = line;
+ char *name;
+
+ if (*line == '"') {
+ /* Proposed "new-style" GNU patch/diff format; see
+ * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
+ */
+ name = unquote_c_style(line, NULL);
+ if (name) {
+ char *cp = name;
+ while (p_value) {
+ cp = strchr(name, '/');
+ if (!cp)
+ break;
+ cp++;
+ p_value--;
+ }
+ if (cp) {
+ /* name can later be freed, so we need
+ * to memmove, not just return cp
+ */
+ memmove(name, cp, strlen(cp) + 1);
+ free(def);
+ return name;
+ }
+ else {
+ free(name);
+ name = NULL;
+ }
+ }
+ }
+
+ for (;;) {
+ char c = *line;
+
+ if (isspace(c)) {
+ if (c == '\n')
+ break;
+ if (name_terminate(start, line-start, c, terminate))
+ break;
+ }
+ line++;
+ if (c == '/' && !--p_value)
+ start = line;
+ }
+ if (!start)
+ return def;
+ len = line - start;
+ if (!len)
+ return def;
+
+ /*
+ * Generally we prefer the shorter name, especially
+ * if the other one is just a variation of that with
+ * something else tacked on to the end (ie "file.orig"
+ * or "file~").
+ */
+ if (def) {
+ int deflen = strlen(def);
+ if (deflen < len && !strncmp(start, def, deflen))
+ return def;
+ }
+
+ name = xmalloc(len + 1);
+ memcpy(name, start, len);
+ name[len] = 0;
+ free(def);
+ return name;
+}
+
+/*
+ * Get the name etc info from the --/+++ lines of a traditional patch header
+ *
+ * NOTE! This hardcodes "-p1" behaviour in filename detection.
+ *
+ * FIXME! The end-of-filename heuristics are kind of screwy. For existing
+ * files, we can happily check the index for a match, but for creating a
+ * new file we should try to match whatever "patch" does. I have no idea.
+ */
+static void parse_traditional_patch(const char *first, const char *second, struct patch *patch)
+{
+ int p_value = 1;
+ char *name;
+
+ first += 4; // skip "--- "
+ second += 4; // skip "+++ "
+ if (is_dev_null(first)) {
+ patch->is_new = 1;
+ patch->is_delete = 0;
+ name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB);
+ patch->new_name = name;
+ } else if (is_dev_null(second)) {
+ patch->is_new = 0;
+ patch->is_delete = 1;
+ name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+ patch->old_name = name;
+ } else {
+ name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
+ name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
+ patch->old_name = patch->new_name = name;
+ }
+ if (!name)
+ die("unable to find filename in patch at line %d", linenr);
+}
+
+static int gitdiff_hdrend(const char *line, struct patch *patch)
+{
+ return -1;
+}
+
+/*
+ * We're anal about diff header consistency, to make
+ * sure that we don't end up having strange ambiguous
+ * patches floating around.
+ *
+ * As a result, gitdiff_{old|new}name() will check
+ * their names against any previous information, just
+ * to make sure..
+ */
+static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
+{
+ if (!orig_name && !isnull)
+ return find_name(line, NULL, 1, 0);
+
+ if (orig_name) {
+ int len;
+ const char *name;
+ char *another;
+ name = orig_name;
+ len = strlen(name);
+ if (isnull)
+ die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
+ another = find_name(line, NULL, 1, 0);
+ if (!another || memcmp(another, name, len))
+ die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
+ free(another);
+ return orig_name;
+ }
+ else {
+ /* expect "/dev/null" */
+ if (memcmp("/dev/null", line, 9) || line[9] != '\n')
+ die("git-apply: bad git-diff - expected /dev/null on line %d", linenr);
+ return NULL;
+ }
+}
+
+static int gitdiff_oldname(const char *line, struct patch *patch)
+{
+ patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, "old");
+ return 0;
+}
+
+static int gitdiff_newname(const char *line, struct patch *patch)
+{
+ patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, "new");
+ return 0;
+}
+
+static int gitdiff_oldmode(const char *line, struct patch *patch)
+{
+ patch->old_mode = strtoul(line, NULL, 8);
+ return 0;
+}
+
+static int gitdiff_newmode(const char *line, struct patch *patch)
+{
+ patch->new_mode = strtoul(line, NULL, 8);
+ return 0;
+}
+
+static int gitdiff_delete(const char *line, struct patch *patch)
+{
+ patch->is_delete = 1;
+ patch->old_name = patch->def_name;
+ return gitdiff_oldmode(line, patch);
+}
+
+static int gitdiff_newfile(const char *line, struct patch *patch)
+{
+ patch->is_new = 1;
+ patch->new_name = patch->def_name;
+ return gitdiff_newmode(line, patch);
+}
+
+static int gitdiff_copysrc(const char *line, struct patch *patch)
+{
+ patch->is_copy = 1;
+ patch->old_name = find_name(line, NULL, 0, 0);
+ return 0;
+}
+
+static int gitdiff_copydst(const char *line, struct patch *patch)
+{
+ patch->is_copy = 1;
+ patch->new_name = find_name(line, NULL, 0, 0);
+ return 0;
+}
+
+static int gitdiff_renamesrc(const char *line, struct patch *patch)
+{
+ patch->is_rename = 1;
+ patch->old_name = find_name(line, NULL, 0, 0);
+ return 0;
+}
+
+static int gitdiff_renamedst(const char *line, struct patch *patch)
+{
+ patch->is_rename = 1;
+ patch->new_name = find_name(line, NULL, 0, 0);
+ return 0;
+}
+
+static int gitdiff_similarity(const char *line, struct patch *patch)
+{
+ if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+ patch->score = 0;
+ return 0;
+}
+
+static int gitdiff_dissimilarity(const char *line, struct patch *patch)
+{
+ if ((patch->score = strtoul(line, NULL, 10)) == ULONG_MAX)
+ patch->score = 0;
+ return 0;
+}
+
+static int gitdiff_index(const char *line, struct patch *patch)
+{
+ /* index line is N hexadecimal, "..", N hexadecimal,
+ * and optional space with octal mode.
+ */
+ const char *ptr, *eol;
+ int len;
+
+ ptr = strchr(line, '.');
+ if (!ptr || ptr[1] != '.' || 40 < ptr - line)
+ return 0;
+ len = ptr - line;
+ memcpy(patch->old_sha1_prefix, line, len);
+ patch->old_sha1_prefix[len] = 0;
+
+ line = ptr + 2;
+ ptr = strchr(line, ' ');
+ eol = strchr(line, '\n');
+
+ if (!ptr || eol < ptr)
+ ptr = eol;
+ len = ptr - line;
+
+ if (40 < len)
+ return 0;
+ memcpy(patch->new_sha1_prefix, line, len);
+ patch->new_sha1_prefix[len] = 0;
+ if (*ptr == ' ')
+ patch->new_mode = patch->old_mode = strtoul(ptr+1, NULL, 8);
+ return 0;
+}
+
+/*
+ * This is normal for a diff that doesn't change anything: we'll fall through
+ * into the next diff. Tell the parser to break out.
+ */
+static int gitdiff_unrecognized(const char *line, struct patch *patch)
+{
+ return -1;
+}
+
+static const char *stop_at_slash(const char *line, int llen)
+{
+ int i;
+
+ for (i = 0; i < llen; i++) {
+ int ch = line[i];
+ if (ch == '/')
+ return line + i;
+ }
+ return NULL;
+}
+
+/* This is to extract the same name that appears on "diff --git"
+ * line. We do not find and return anything if it is a rename
+ * patch, and it is OK because we will find the name elsewhere.
+ * We need to reliably find name only when it is mode-change only,
+ * creation or deletion of an empty file. In any of these cases,
+ * both sides are the same name under a/ and b/ respectively.
+ */
+static char *git_header_name(char *line, int llen)
+{
+ int len;
+ const char *name;
+ const char *second = NULL;
+
+ line += strlen("diff --git ");
+ llen -= strlen("diff --git ");
+
+ if (*line == '"') {
+ const char *cp;
+ char *first = unquote_c_style(line, &second);
+ if (!first)
+ return NULL;
+
+ /* advance to the first slash */
+ cp = stop_at_slash(first, strlen(first));
+ if (!cp || cp == first) {
+ /* we do not accept absolute paths */
+ free_first_and_fail:
+ free(first);
+ return NULL;
+ }
+ len = strlen(cp+1);
+ memmove(first, cp+1, len+1); /* including NUL */
+
+ /* second points at one past closing dq of name.
+ * find the second name.
+ */
+ while ((second < line + llen) && isspace(*second))
+ second++;
+
+ if (line + llen <= second)
+ goto free_first_and_fail;
+ if (*second == '"') {
+ char *sp = unquote_c_style(second, NULL);
+ if (!sp)
+ goto free_first_and_fail;
+ cp = stop_at_slash(sp, strlen(sp));
+ if (!cp || cp == sp) {
+ free_both_and_fail:
+ free(sp);
+ goto free_first_and_fail;
+ }
+ /* They must match, otherwise ignore */
+ if (strcmp(cp+1, first))
+ goto free_both_and_fail;
+ free(sp);
+ return first;
+ }
+
+ /* unquoted second */
+ cp = stop_at_slash(second, line + llen - second);
+ if (!cp || cp == second)
+ goto free_first_and_fail;
+ cp++;
+ if (line + llen - cp != len + 1 ||
+ memcmp(first, cp, len))
+ goto free_first_and_fail;
+ return first;
+ }
+
+ /* unquoted first name */
+ name = stop_at_slash(line, llen);
+ if (!name || name == line)
+ return NULL;
+
+ name++;
+
+ /* since the first name is unquoted, a dq if exists must be
+ * the beginning of the second name.
+ */
+ for (second = name; second < line + llen; second++) {
+ if (*second == '"') {
+ const char *cp = second;
+ const char *np;
+ char *sp = unquote_c_style(second, NULL);
+
+ if (!sp)
+ return NULL;
+ np = stop_at_slash(sp, strlen(sp));
+ if (!np || np == sp) {
+ free_second_and_fail:
+ free(sp);
+ return NULL;
+ }
+ np++;
+ len = strlen(np);
+ if (len < cp - name &&
+ !strncmp(np, name, len) &&
+ isspace(name[len])) {
+ /* Good */
+ memmove(sp, np, len + 1);
+ return sp;
+ }
+ goto free_second_and_fail;
+ }
+ }
+
+ /*
+ * Accept a name only if it shows up twice, exactly the same
+ * form.
+ */
+ for (len = 0 ; ; len++) {
+ char c = name[len];
+
+ switch (c) {
+ default:
+ continue;
+ case '\n':
+ return NULL;
+ case '\t': case ' ':
+ second = name+len;
+ for (;;) {
+ char c = *second++;
+ if (c == '\n')
+ return NULL;
+ if (c == '/')
+ break;
+ }
+ if (second[len] == '\n' && !memcmp(name, second, len)) {
+ char *ret = xmalloc(len + 1);
+ memcpy(ret, name, len);
+ ret[len] = 0;
+ return ret;
+ }
+ }
+ }
+ return NULL;
+}
+
+/* Verify that we recognize the lines following a git header */
+static int parse_git_header(char *line, int len, unsigned int size, struct patch *patch)
+{
+ unsigned long offset;
+
+ /* A git diff has explicit new/delete information, so we don't guess */
+ patch->is_new = 0;
+ patch->is_delete = 0;
+
+ /*
+ * Some things may not have the old name in the
+ * rest of the headers anywhere (pure mode changes,
+ * or removing or adding empty files), so we get
+ * the default name from the header.
+ */
+ patch->def_name = git_header_name(line, len);
+
+ line += len;
+ size -= len;
+ linenr++;
+ for (offset = len ; size > 0 ; offset += len, size -= len, line += len, linenr++) {
+ static const struct opentry {
+ const char *str;
+ int (*fn)(const char *, struct patch *);
+ } optable[] = {
+ { "@@ -", gitdiff_hdrend },
+ { "--- ", gitdiff_oldname },
+ { "+++ ", gitdiff_newname },
+ { "old mode ", gitdiff_oldmode },
+ { "new mode ", gitdiff_newmode },
+ { "deleted file mode ", gitdiff_delete },
+ { "new file mode ", gitdiff_newfile },
+ { "copy from ", gitdiff_copysrc },
+ { "copy to ", gitdiff_copydst },
+ { "rename old ", gitdiff_renamesrc },
+ { "rename new ", gitdiff_renamedst },
+ { "rename from ", gitdiff_renamesrc },
+ { "rename to ", gitdiff_renamedst },
+ { "similarity index ", gitdiff_similarity },
+ { "dissimilarity index ", gitdiff_dissimilarity },
+ { "index ", gitdiff_index },
+ { "", gitdiff_unrecognized },
+ };
+ int i;
+
+ len = linelen(line, size);
+ if (!len || line[len-1] != '\n')
+ break;
+ for (i = 0; i < sizeof(optable) / sizeof(optable[0]); i++) {
+ const struct opentry *p = optable + i;
+ int oplen = strlen(p->str);
+ if (len < oplen || memcmp(p->str, line, oplen))
+ continue;
+ if (p->fn(line + oplen, patch) < 0)
+ return offset;
+ break;
+ }
+ }
+
+ return offset;
+}
+
+static int parse_num(const char *line, unsigned long *p)
+{
+ char *ptr;
+
+ if (!isdigit(*line))
+ return 0;
+ *p = strtoul(line, &ptr, 10);
+ return ptr - line;
+}
+
+static int parse_range(const char *line, int len, int offset, const char *expect,
+ unsigned long *p1, unsigned long *p2)
+{
+ int digits, ex;
+
+ if (offset < 0 || offset >= len)
+ return -1;
+ line += offset;
+ len -= offset;
+
+ digits = parse_num(line, p1);
+ if (!digits)
+ return -1;
+
+ offset += digits;
+ line += digits;
+ len -= digits;
+
+ *p2 = *p1;
+ if (*line == ',') {
+ digits = parse_num(line+1, p2);
+ if (!digits)
+ return -1;
+
+ offset += digits+1;
+ line += digits+1;
+ len -= digits+1;
+ }
+
+ ex = strlen(expect);
+ if (ex > len)
+ return -1;
+ if (memcmp(line, expect, ex))
+ return -1;
+
+ return offset + ex;
+}
+
+/*
+ * Parse a unified diff fragment header of the
+ * form "@@ -a,b +c,d @@"
+ */
+static int parse_fragment_header(char *line, int len, struct fragment *fragment)
+{
+ int offset;
+
+ if (!len || line[len-1] != '\n')
+ return -1;
+
+ /* Figure out the number of lines in a fragment */
+ offset = parse_range(line, len, 4, " +", &fragment->oldpos, &fragment->oldlines);
+ offset = parse_range(line, len, offset, " @@", &fragment->newpos, &fragment->newlines);
+
+ return offset;
+}
+
+static int find_header(char *line, unsigned long size, int *hdrsize, struct patch *patch)
+{
+ unsigned long offset, len;
+
+ patch->is_rename = patch->is_copy = 0;
+ patch->is_new = patch->is_delete = -1;
+ patch->old_mode = patch->new_mode = 0;
+ patch->old_name = patch->new_name = NULL;
+ for (offset = 0; size > 0; offset += len, size -= len, line += len, linenr++) {
+ unsigned long nextlen;
+
+ len = linelen(line, size);
+ if (!len)
+ break;
+
+ /* Testing this early allows us to take a few shortcuts.. */
+ if (len < 6)
+ continue;
+
+ /*
+ * Make sure we don't find any unconnected patch fragmants.
+ * That's a sign that we didn't find a header, and that a
+ * patch has become corrupted/broken up.
+ */
+ if (!memcmp("@@ -", line, 4)) {
+ struct fragment dummy;
+ if (parse_fragment_header(line, len, &dummy) < 0)
+ continue;
+ error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line);
+ }
+
+ if (size < len + 6)
+ break;
+
+ /*
+ * Git patch? It might not have a real patch, just a rename
+ * or mode change, so we handle that specially
+ */
+ if (!memcmp("diff --git ", line, 11)) {
+ int git_hdr_len = parse_git_header(line, len, size, patch);
+ if (git_hdr_len <= len)
+ continue;
+ if (!patch->old_name && !patch->new_name) {
+ if (!patch->def_name)
+ die("git diff header lacks filename information (line %d)", linenr);
+ patch->old_name = patch->new_name = patch->def_name;
+ }
+ *hdrsize = git_hdr_len;
+ return offset;
+ }
+
+ /** --- followed by +++ ? */
+ if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4))
+ continue;
+
+ /*
+ * We only accept unified patches, so we want it to
+ * at least have "@@ -a,b +c,d @@\n", which is 14 chars
+ * minimum
+ */
+ nextlen = linelen(line + len, size - len);
+ if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4))
+ continue;
+
+ /* Ok, we'll consider it a patch */
+ parse_traditional_patch(line, line+len, patch);
+ *hdrsize = len + nextlen;
+ linenr += 2;
+ return offset;
+ }
+ return -1;
+}
+
+/*
+ * Parse a unified diff. Note that this really needs
+ * to parse each fragment separately, since the only
+ * way to know the difference between a "---" that is
+ * part of a patch, and a "---" that starts the next
+ * patch is to look at the line counts..
+ */
+static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
+{
+ int added, deleted;
+ int len = linelen(line, size), offset;
+ unsigned long oldlines, newlines;
+
+ offset = parse_fragment_header(line, len, fragment);
+ if (offset < 0)
+ return -1;
+ oldlines = fragment->oldlines;
+ newlines = fragment->newlines;
+
+ if (patch->is_new < 0) {
+ patch->is_new = !oldlines;
+ if (!oldlines)
+ patch->old_name = NULL;
+ }
+ if (patch->is_delete < 0) {
+ patch->is_delete = !newlines;
+ if (!newlines)
+ patch->new_name = NULL;
+ }
+
+ if (patch->is_new != !oldlines)
+ return error("new file depends on old contents");
+ if (patch->is_delete != !newlines) {
+ if (newlines)
+ return error("deleted file still has contents");
+ fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
+ }
+
+ /* Parse the thing.. */
+ line += len;
+ size -= len;
+ linenr++;
+ added = deleted = 0;
+ for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
+ if (!oldlines && !newlines)
+ break;
+ len = linelen(line, size);
+ if (!len || line[len-1] != '\n')
+ return -1;
+ switch (*line) {
+ default:
+ return -1;
+ case ' ':
+ oldlines--;
+ newlines--;
+ break;
+ case '-':
+ deleted++;
+ oldlines--;
+ break;
+ case '+':
+ added++;
+ newlines--;
+ break;
+
+ /* We allow "\ No newline at end of file". Depending
+ * on locale settings when the patch was produced we
+ * don't know what this line looks like. The only
+ * thing we do know is that it begins with "\ ".
+ * Checking for 12 is just for sanity check -- any
+ * l10n of "\ No newline..." is at least that long.
+ */
+ case '\\':
+ if (len < 12 || memcmp(line, "\\ ", 2))
+ return -1;
+ break;
+ }
+ }
+ /* If a fragment ends with an incomplete line, we failed to include
+ * it in the above loop because we hit oldlines == newlines == 0
+ * before seeing it.
+ */
+ if (12 < size && !memcmp(line, "\\ ", 2))
+ offset += linelen(line, size);
+
+ patch->lines_added += added;
+ patch->lines_deleted += deleted;
+ return offset;
+}
+
+static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
+{
+ unsigned long offset = 0;
+ struct fragment **fragp = &patch->fragments;
+
+ while (size > 4 && !memcmp(line, "@@ -", 4)) {
+ struct fragment *fragment;
+ int len;
+
+ fragment = xmalloc(sizeof(*fragment));
+ memset(fragment, 0, sizeof(*fragment));
+ len = parse_fragment(line, size, patch, fragment);
+ if (len <= 0)
+ die("corrupt patch at line %d", linenr);
+
+ fragment->patch = line;
+ fragment->size = len;
+
+ *fragp = fragment;
+ fragp = &fragment->next;
+
+ offset += len;
+ line += len;
+ size -= len;
+ }
+ return offset;
+}
+
+static inline int metadata_changes(struct patch *patch)
+{
+ return patch->is_rename > 0 ||
+ patch->is_copy > 0 ||
+ patch->is_new > 0 ||
+ patch->is_delete ||
+ (patch->old_mode && patch->new_mode &&
+ patch->old_mode != patch->new_mode);
+}
+
+static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
+{
+ int hdrsize, patchsize;
+ int offset = find_header(buffer, size, &hdrsize, patch);
+
+ if (offset < 0)
+ return offset;
+
+ patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch);
+
+ if (!patchsize) {
+ static const char *binhdr[] = {
+ "Binary files ",
+ "Files ",
+ NULL,
+ };
+ int i;
+ int hd = hdrsize + offset;
+ unsigned long llen = linelen(buffer + hd, size - hd);
+
+ if (!memcmp(" differ\n", buffer + hd + llen - 8, 8))
+ for (i = 0; binhdr[i]; i++) {
+ int len = strlen(binhdr[i]);
+ if (len < size - hd &&
+ !memcmp(binhdr[i], buffer + hd, len)) {
+ patch->is_binary = 1;
+ break;
+ }
+ }
+
+ /* Empty patch cannot be applied if:
+ * - it is a binary patch and we do not do binary_replace, or
+ * - text patch without metadata change
+ */
+ if ((apply || check) &&
+ (patch->is_binary
+ ? !allow_binary_replacement
+ : !metadata_changes(patch)))
+ die("patch with only garbage at line %d", linenr);
+ }
+
+ return offset + hdrsize + patchsize;
+}
+
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
+
+static void show_stats(struct patch *patch)
+{
+ const char *prefix = "";
+ char *name = patch->new_name;
+ char *qname = NULL;
+ int len, max, add, del, total;
+
+ if (!name)
+ name = patch->old_name;
+
+ if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+ qname = xmalloc(len + 1);
+ quote_c_style(name, qname, NULL, 0);
+ name = qname;
+ }
+
+ /*
+ * "scale" the filename
+ */
+ len = strlen(name);
+ max = max_len;
+ if (max > 50)
+ max = 50;
+ if (len > max) {
+ char *slash;
+ prefix = "...";
+ max -= 3;
+ name += len - max;
+ slash = strchr(name, '/');
+ if (slash)
+ name = slash;
+ }
+ len = max;
+
+ /*
+ * scale the add/delete
+ */
+ max = max_change;
+ if (max + len > 70)
+ max = 70 - len;
+
+ add = patch->lines_added;
+ del = patch->lines_deleted;
+ total = add + del;
+
+ if (max_change > 0) {
+ total = (total * max + max_change / 2) / max_change;
+ add = (add * max + max_change / 2) / max_change;
+ del = total - add;
+ }
+ if (patch->is_binary)
+ printf(" %s%-*s | Bin\n", prefix, len, name);
+ else
+ printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+ len, name, patch->lines_added + patch->lines_deleted,
+ add, pluses, del, minuses);
+ if (qname)
+ free(qname);
+}
+
+static int read_old_data(struct stat *st, const char *path, void *buf, unsigned long size)
+{
+ int fd;
+ unsigned long got;
+
+ switch (st->st_mode & S_IFMT) {
+ case S_IFLNK:
+ return readlink(path, buf, size);
+ case S_IFREG:
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return error("unable to open %s", path);
+ got = 0;
+ for (;;) {
+ int ret = read(fd, buf + got, size - got);
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ continue;
+ break;
+ }
+ if (!ret)
+ break;
+ got += ret;
+ }
+ close(fd);
+ return got;
+
+ default:
+ return -1;
+ }
+}
+
+static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line)
+{
+ int i;
+ unsigned long start, backwards, forwards;
+
+ if (fragsize > size)
+ return -1;
+
+ start = 0;
+ if (line > 1) {
+ unsigned long offset = 0;
+ i = line-1;
+ while (offset + fragsize <= size) {
+ if (buf[offset++] == '\n') {
+ start = offset;
+ if (!--i)
+ break;
+ }
+ }
+ }
+
+ /* Exact line number? */
+ if (!memcmp(buf + start, fragment, fragsize))
+ return start;
+
+ /*
+ * There's probably some smart way to do this, but I'll leave
+ * that to the smart and beautiful people. I'm simple and stupid.
+ */
+ backwards = start;
+ forwards = start;
+ for (i = 0; ; i++) {
+ unsigned long try;
+ int n;
+
+ /* "backward" */
+ if (i & 1) {
+ if (!backwards) {
+ if (forwards + fragsize > size)
+ break;
+ continue;
+ }
+ do {
+ --backwards;
+ } while (backwards && buf[backwards-1] != '\n');
+ try = backwards;
+ } else {
+ while (forwards + fragsize <= size) {
+ if (buf[forwards++] == '\n')
+ break;
+ }
+ try = forwards;
+ }
+
+ if (try + fragsize > size)
+ continue;
+ if (memcmp(buf + try, fragment, fragsize))
+ continue;
+ n = (i >> 1)+1;
+ if (i & 1)
+ n = -n;
+ return try;
+ }
+
+ /*
+ * We should start searching forward and backward.
+ */
+ return -1;
+}
+
+struct buffer_desc {
+ char *buffer;
+ unsigned long size;
+ unsigned long alloc;
+};
+
+static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
+{
+ char *buf = desc->buffer;
+ const char *patch = frag->patch;
+ int offset, size = frag->size;
+ char *old = xmalloc(size);
+ char *new = xmalloc(size);
+ int oldsize = 0, newsize = 0;
+
+ while (size > 0) {
+ int len = linelen(patch, size);
+ int plen;
+
+ if (!len)
+ break;
+
+ /*
+ * "plen" is how much of the line we should use for
+ * the actual patch data. Normally we just remove the
+ * first character on the line, but if the line is
+ * followed by "\ No newline", then we also remove the
+ * last one (which is the newline, of course).
+ */
+ plen = len-1;
+ if (len < size && patch[len] == '\\')
+ plen--;
+ switch (*patch) {
+ case ' ':
+ case '-':
+ memcpy(old + oldsize, patch + 1, plen);
+ oldsize += plen;
+ if (*patch == '-')
+ break;
+ /* Fall-through for ' ' */
+ case '+':
+ if (*patch != '+' || !no_add) {
+ memcpy(new + newsize, patch + 1, plen);
+ newsize += plen;
+ }
+ break;
+ case '@': case '\\':
+ /* Ignore it, we already handled it */
+ break;
+ default:
+ return -1;
+ }
+ patch += len;
+ size -= len;
+ }
+
+ offset = find_offset(buf, desc->size, old, oldsize, frag->newpos);
+ if (offset >= 0) {
+ int diff = newsize - oldsize;
+ unsigned long size = desc->size + diff;
+ unsigned long alloc = desc->alloc;
+
+ if (size > alloc) {
+ alloc = size + 8192;
+ desc->alloc = alloc;
+ buf = xrealloc(buf, alloc);
+ desc->buffer = buf;
+ }
+ desc->size = size;
+ memmove(buf + offset + newsize, buf + offset + oldsize, size - offset - newsize);
+ memcpy(buf + offset, new, newsize);
+ offset = 0;
+ }
+
+ free(old);
+ free(new);
+ return offset;
+}
+
+static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
+{
+ struct fragment *frag = patch->fragments;
+ const char *name = patch->old_name ? patch->old_name : patch->new_name;
+
+ if (patch->is_binary) {
+ unsigned char sha1[20];
+
+ if (!allow_binary_replacement)
+ return error("cannot apply binary patch to '%s' "
+ "without --allow-binary-replacement",
+ name);
+
+ /* For safety, we require patch index line to contain
+ * full 40-byte textual SHA1 for old and new, at least for now.
+ */
+ if (strlen(patch->old_sha1_prefix) != 40 ||
+ strlen(patch->new_sha1_prefix) != 40 ||
+ get_sha1_hex(patch->old_sha1_prefix, sha1) ||
+ get_sha1_hex(patch->new_sha1_prefix, sha1))
+ return error("cannot apply binary patch to '%s' "
+ "without full index line", name);
+
+ if (patch->old_name) {
+ unsigned char hdr[50];
+ int hdrlen;
+
+ /* See if the old one matches what the patch
+ * applies to.
+ */
+ write_sha1_file_prepare(desc->buffer, desc->size,
+ "blob", sha1, hdr, &hdrlen);
+ if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
+ return error("the patch applies to '%s' (%s), "
+ "which does not match the "
+ "current contents.",
+ name, sha1_to_hex(sha1));
+ }
+ else {
+ /* Otherwise, the old one must be empty. */
+ if (desc->size)
+ return error("the patch applies to an empty "
+ "'%s' but it is not empty", name);
+ }
+
+ /* For now, we do not record post-image data in the patch,
+ * and require the object already present in the recipient's
+ * object database.
+ */
+ if (desc->buffer) {
+ free(desc->buffer);
+ desc->alloc = desc->size = 0;
+ }
+ get_sha1_hex(patch->new_sha1_prefix, sha1);
+
+ if (memcmp(sha1, null_sha1, 20)) {
+ char type[10];
+ unsigned long size;
+
+ desc->buffer = read_sha1_file(sha1, type, &size);
+ if (!desc->buffer)
+ return error("the necessary postimage %s for "
+ "'%s' does not exist",
+ patch->new_sha1_prefix, name);
+ desc->alloc = desc->size = size;
+ }
+
+ return 0;
+ }
+
+ while (frag) {
+ if (apply_one_fragment(desc, frag) < 0)
+ return error("patch failed: %s:%ld",
+ name, frag->oldpos);
+ frag = frag->next;
+ }
+ return 0;
+}
+
+static int apply_data(struct patch *patch, struct stat *st)
+{
+ char *buf;
+ unsigned long size, alloc;
+ struct buffer_desc desc;
+
+ size = 0;
+ alloc = 0;
+ buf = NULL;
+ if (patch->old_name) {
+ size = st->st_size;
+ alloc = size + 8192;
+ buf = xmalloc(alloc);
+ if (read_old_data(st, patch->old_name, buf, alloc) != size)
+ return error("read of %s failed", patch->old_name);
+ }
+
+ desc.size = size;
+ desc.alloc = alloc;
+ desc.buffer = buf;
+ if (apply_fragments(&desc, patch) < 0)
+ return -1;
+ patch->result = desc.buffer;
+ patch->resultsize = desc.size;
+
+ if (patch->is_delete && patch->resultsize)
+ return error("removal patch leaves file contents");
+
+ return 0;
+}
+
+static int check_patch(struct patch *patch)
+{
+ struct stat st;
+ const char *old_name = patch->old_name;
+ const char *new_name = patch->new_name;
+ const char *name = old_name ? old_name : new_name;
+
+ if (old_name) {
+ int changed;
+ int stat_ret = lstat(old_name, &st);
+
+ if (check_index) {
+ int pos = cache_name_pos(old_name, strlen(old_name));
+ if (pos < 0)
+ return error("%s: does not exist in index",
+ old_name);
+ if (stat_ret < 0) {
+ struct checkout costate;
+ if (errno != ENOENT)
+ return error("%s: %s", old_name,
+ strerror(errno));
+ /* checkout */
+ costate.base_dir = "";
+ costate.base_dir_len = 0;
+ costate.force = 0;
+ costate.quiet = 0;
+ costate.not_new = 0;
+ costate.refresh_cache = 1;
+ if (checkout_entry(active_cache[pos],
+ &costate) ||
+ lstat(old_name, &st))
+ return -1;
+ }
+
+ changed = ce_match_stat(active_cache[pos], &st);
+ if (changed)
+ return error("%s: does not match index",
+ old_name);
+ }
+ else if (stat_ret < 0)
+ return error("%s: %s", old_name, strerror(errno));
+
+ if (patch->is_new < 0)
+ patch->is_new = 0;
+ st.st_mode = ntohl(create_ce_mode(st.st_mode));
+ if (!patch->old_mode)
+ patch->old_mode = st.st_mode;
+ if ((st.st_mode ^ patch->old_mode) & S_IFMT)
+ return error("%s: wrong type", old_name);
+ if (st.st_mode != patch->old_mode)
+ fprintf(stderr, "warning: %s has type %o, expected %o\n",
+ old_name, st.st_mode, patch->old_mode);
+ }
+
+ if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
+ if (check_index && cache_name_pos(new_name, strlen(new_name)) >= 0)
+ return error("%s: already exists in index", new_name);
+ if (!lstat(new_name, &st))
+ return error("%s: already exists in working directory", new_name);
+ if (errno != ENOENT)
+ return error("%s: %s", new_name, strerror(errno));
+ if (!patch->new_mode) {
+ if (patch->is_new)
+ patch->new_mode = S_IFREG | 0644;
+ else
+ patch->new_mode = patch->old_mode;
+ }
+ }
+
+ if (new_name && old_name) {
+ int same = !strcmp(old_name, new_name);
+ if (!patch->new_mode)
+ patch->new_mode = patch->old_mode;
+ if ((patch->old_mode ^ patch->new_mode) & S_IFMT)
+ return error("new mode (%o) of %s does not match old mode (%o)%s%s",
+ patch->new_mode, new_name, patch->old_mode,
+ same ? "" : " of ", same ? "" : old_name);
+ }
+
+ if (apply_data(patch, &st) < 0)
+ return error("%s: patch does not apply", name);
+ return 0;
+}
+
+static int check_patch_list(struct patch *patch)
+{
+ int error = 0;
+
+ for (;patch ; patch = patch->next)
+ error |= check_patch(patch);
+ return error;
+}
+
+static inline int is_null_sha1(const unsigned char *sha1)
+{
+ return !memcmp(sha1, null_sha1, 20);
+}
+
+static void show_index_list(struct patch *list)
+{
+ struct patch *patch;
+
+ /* Once we start supporting the reverse patch, it may be
+ * worth showing the new sha1 prefix, but until then...
+ */
+ for (patch = list; patch; patch = patch->next) {
+ const unsigned char *sha1_ptr;
+ unsigned char sha1[20];
+ const char *name;
+
+ name = patch->old_name ? patch->old_name : patch->new_name;
+ if (patch->is_new)
+ sha1_ptr = null_sha1;
+ else if (get_sha1(patch->old_sha1_prefix, sha1))
+ die("sha1 information is lacking or useless (%s).",
+ name);
+ else
+ sha1_ptr = sha1;
+
+ printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr));
+ if (line_termination && quote_c_style(name, NULL, NULL, 0))
+ quote_c_style(name, NULL, stdout, 0);
+ else
+ fputs(name, stdout);
+ putchar(line_termination);
+ }
+}
+
+static void stat_patch_list(struct patch *patch)
+{
+ int files, adds, dels;
+
+ for (files = adds = dels = 0 ; patch ; patch = patch->next) {
+ files++;
+ adds += patch->lines_added;
+ dels += patch->lines_deleted;
+ show_stats(patch);
+ }
+
+ printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels);
+}
+
+static void numstat_patch_list(struct patch *patch)
+{
+ for ( ; patch; patch = patch->next) {
+ const char *name;
+ name = patch->old_name ? patch->old_name : patch->new_name;
+ printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+ if (line_termination && quote_c_style(name, NULL, NULL, 0))
+ quote_c_style(name, NULL, stdout, 0);
+ else
+ fputs(name, stdout);
+ putchar('\n');
+ }
+}
+
+static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
+{
+ if (mode)
+ printf(" %s mode %06o %s\n", newdelete, mode, name);
+ else
+ printf(" %s %s\n", newdelete, name);
+}
+
+static void show_mode_change(struct patch *p, int show_name)
+{
+ if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
+ if (show_name)
+ printf(" mode change %06o => %06o %s\n",
+ p->old_mode, p->new_mode, p->new_name);
+ else
+ printf(" mode change %06o => %06o\n",
+ p->old_mode, p->new_mode);
+ }
+}
+
+static void show_rename_copy(struct patch *p)
+{
+ const char *renamecopy = p->is_rename ? "rename" : "copy";
+ const char *old, *new;
+
+ /* Find common prefix */
+ old = p->old_name;
+ new = p->new_name;
+ while (1) {
+ const char *slash_old, *slash_new;
+ slash_old = strchr(old, '/');
+ slash_new = strchr(new, '/');
+ if (!slash_old ||
+ !slash_new ||
+ slash_old - old != slash_new - new ||
+ memcmp(old, new, slash_new - new))
+ break;
+ old = slash_old + 1;
+ new = slash_new + 1;
+ }
+ /* p->old_name thru old is the common prefix, and old and new
+ * through the end of names are renames
+ */
+ if (old != p->old_name)
+ printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
+ (int)(old - p->old_name), p->old_name,
+ old, new, p->score);
+ else
+ printf(" %s %s => %s (%d%%)\n", renamecopy,
+ p->old_name, p->new_name, p->score);
+ show_mode_change(p, 0);
+}
+
+static void summary_patch_list(struct patch *patch)
+{
+ struct patch *p;
+
+ for (p = patch; p; p = p->next) {
+ if (p->is_new)
+ show_file_mode_name("create", p->new_mode, p->new_name);
+ else if (p->is_delete)
+ show_file_mode_name("delete", p->old_mode, p->old_name);
+ else {
+ if (p->is_rename || p->is_copy)
+ show_rename_copy(p);
+ else {
+ if (p->score) {
+ printf(" rewrite %s (%d%%)\n",
+ p->new_name, p->score);
+ show_mode_change(p, 0);
+ }
+ else
+ show_mode_change(p, 1);
+ }
+ }
+ }
+}
+
+static void patch_stats(struct patch *patch)
+{
+ int lines = patch->lines_added + patch->lines_deleted;
+
+ if (lines > max_change)
+ max_change = lines;
+ if (patch->old_name) {
+ int len = quote_c_style(patch->old_name, NULL, NULL, 0);
+ if (!len)
+ len = strlen(patch->old_name);
+ if (len > max_len)
+ max_len = len;
+ }
+ if (patch->new_name) {
+ int len = quote_c_style(patch->new_name, NULL, NULL, 0);
+ if (!len)
+ len = strlen(patch->new_name);
+ if (len > max_len)
+ max_len = len;
+ }
+}
+
+static void remove_file(struct patch *patch)
+{
+ if (write_index) {
+ if (remove_file_from_cache(patch->old_name) < 0)
+ die("unable to remove %s from index", patch->old_name);
+ }
+ unlink(patch->old_name);
+}
+
+static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
+{
+ struct stat st;
+ struct cache_entry *ce;
+ int namelen = strlen(path);
+ unsigned ce_size = cache_entry_size(namelen);
+
+ if (!write_index)
+ return;
+
+ ce = xmalloc(ce_size);
+ memset(ce, 0, ce_size);
+ memcpy(ce->name, path, namelen);
+ ce->ce_mode = create_ce_mode(mode);
+ ce->ce_flags = htons(namelen);
+ if (lstat(path, &st) < 0)
+ die("unable to stat newly created file %s", path);
+ fill_stat_cache_info(ce, &st);
+ if (write_sha1_file(buf, size, "blob", ce->sha1) < 0)
+ die("unable to create backing store for newly created file %s", path);
+ if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
+ die("unable to add cache entry for %s", path);
+}
+
+static void create_subdirectories(const char *path)
+{
+ int len = strlen(path);
+ char *buf = xmalloc(len + 1);
+ const char *slash = path;
+
+ while ((slash = strchr(slash+1, '/')) != NULL) {
+ len = slash - path;
+ memcpy(buf, path, len);
+ buf[len] = 0;
+ if (mkdir(buf, 0777) < 0) {
+ if (errno != EEXIST)
+ break;
+ }
+ }
+ free(buf);
+}
+
+static int try_create_file(const char *path, unsigned int mode, const char *buf, unsigned long size)
+{
+ int fd;
+
+ if (S_ISLNK(mode))
+ return symlink(buf, path);
+ fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_TRUNC, (mode & 0100) ? 0777 : 0666);
+ if (fd < 0)
+ return -1;
+ while (size) {
+ int written = write(fd, buf, size);
+ if (written < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ die("writing file %s: %s", path, strerror(errno));
+ }
+ if (!written)
+ die("out of space writing file %s", path);
+ buf += written;
+ size -= written;
+ }
+ if (close(fd) < 0)
+ die("closing file %s: %s", path, strerror(errno));
+ return 0;
+}
+
+/*
+ * We optimistically assume that the directories exist,
+ * which is true 99% of the time anyway. If they don't,
+ * we create them and try again.
+ */
+static void create_one_file(const char *path, unsigned mode, const char *buf, unsigned long size)
+{
+ if (!try_create_file(path, mode, buf, size))
+ return;
+
+ if (errno == ENOENT) {
+ create_subdirectories(path);
+ if (!try_create_file(path, mode, buf, size))
+ return;
+ }
+
+ if (errno == EEXIST) {
+ unsigned int nr = getpid();
+
+ for (;;) {
+ const char *newpath;
+ newpath = mkpath("%s~%u", path, nr);
+ if (!try_create_file(newpath, mode, buf, size)) {
+ if (!rename(newpath, path))
+ return;
+ unlink(newpath);
+ break;
+ }
+ if (errno != EEXIST)
+ break;
+ }
+ }
+ die("unable to write file %s mode %o", path, mode);
+}
+
+static void create_file(struct patch *patch)
+{
+ const char *path = patch->new_name;
+ unsigned mode = patch->new_mode;
+ unsigned long size = patch->resultsize;
+ char *buf = patch->result;
+
+ if (!mode)
+ mode = S_IFREG | 0644;
+ create_one_file(path, mode, buf, size);
+ add_index_file(path, mode, buf, size);
+}
+
+static void write_out_one_result(struct patch *patch)
+{
+ if (patch->is_delete > 0) {
+ remove_file(patch);
+ return;
+ }
+ if (patch->is_new > 0 || patch->is_copy) {
+ create_file(patch);
+ return;
+ }
+ /*
+ * Rename or modification boils down to the same
+ * thing: remove the old, write the new
+ */
+ remove_file(patch);
+ create_file(patch);
+}
+
+static void write_out_results(struct patch *list, int skipped_patch)
+{
+ if (!list && !skipped_patch)
+ die("No changes");
+
+ while (list) {
+ write_out_one_result(list);
+ list = list->next;
+ }
+}
+
+static struct cache_file cache_file;
+
+static struct excludes {
+ struct excludes *next;
+ const char *path;
+} *excludes;
+
+static int use_patch(struct patch *p)
+{
+ const char *pathname = p->new_name ? p->new_name : p->old_name;
+ struct excludes *x = excludes;
+ while (x) {
+ if (fnmatch(x->path, pathname, 0) == 0)
+ return 0;
+ x = x->next;
+ }
+ return 1;
+}
+
+static int apply_patch(int fd)
+{
+ int newfd;
+ unsigned long offset, size;
+ char *buffer = read_patch_file(fd, &size);
+ struct patch *list = NULL, **listp = &list;
+ int skipped_patch = 0;
+
+ if (!buffer)
+ return -1;
+ offset = 0;
+ while (size > 0) {
+ struct patch *patch;
+ int nr;
+
+ patch = xmalloc(sizeof(*patch));
+ memset(patch, 0, sizeof(*patch));
+ nr = parse_chunk(buffer + offset, size, patch);
+ if (nr < 0)
+ break;
+ if (use_patch(patch)) {
+ patch_stats(patch);
+ *listp = patch;
+ listp = &patch->next;
+ } else {
+ /* perhaps free it a bit better? */
+ free(patch);
+ skipped_patch++;
+ }
+ offset += nr;
+ size -= nr;
+ }
+
+ newfd = -1;
+ write_index = check_index && apply;
+ if (write_index)
+ newfd = hold_index_file_for_update(&cache_file, get_index_file());
+ if (check_index) {
+ if (read_cache() < 0)
+ die("unable to read index file");
+ }
+
+ if ((check || apply) && check_patch_list(list) < 0)
+ exit(1);
+
+ if (apply)
+ write_out_results(list, skipped_patch);
+
+ if (write_index) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_index_file(&cache_file))
+ die("Unable to write new cachefile");
+ }
+
+ if (show_index_info)
+ show_index_list(list);
+
+ if (diffstat)
+ stat_patch_list(list);
+
+ if (numstat)
+ numstat_patch_list(list);
+
+ if (summary)
+ summary_patch_list(list);
+
+ free(buffer);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ int read_stdin = 1;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ int fd;
+
+ if (!strcmp(arg, "-")) {
+ apply_patch(0);
+ read_stdin = 0;
+ continue;
+ }
+ if (!strncmp(arg, "--exclude=", 10)) {
+ struct excludes *x = xmalloc(sizeof(*x));
+ x->path = arg + 10;
+ x->next = excludes;
+ excludes = x;
+ continue;
+ }
+ if (!strcmp(arg, "--no-add")) {
+ no_add = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--stat")) {
+ apply = 0;
+ diffstat = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--allow-binary-replacement")) {
+ allow_binary_replacement = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--numstat")) {
+ apply = 0;
+ numstat = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--summary")) {
+ apply = 0;
+ summary = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--check")) {
+ apply = 0;
+ check = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--index")) {
+ check_index = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--apply")) {
+ apply = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--index-info")) {
+ apply = 0;
+ show_index_info = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-z")) {
+ line_termination = 0;
+ continue;
+ }
+ fd = open(arg, O_RDONLY);
+ if (fd < 0)
+ usage(apply_usage);
+ read_stdin = 0;
+ apply_patch(fd);
+ close(fd);
+ }
+ if (read_stdin)
+ apply_patch(0);
+ return 0;
+}
diff --git a/arm/sha1.c b/arm/sha1.c
new file mode 100644
index 000000000..11b1a048b
--- /dev/null
+++ b/arm/sha1.c
@@ -0,0 +1,82 @@
+/*
+ * SHA-1 implementation optimized for ARM
+ *
+ * Copyright: (C) 2005 by Nicolas Pitre <nico@cam.org>
+ * Created: September 17, 2005
+ */
+
+#include <string.h>
+#include "sha1.h"
+
+extern void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
+
+void SHA1_Init(SHA_CTX *c)
+{
+ c->len = 0;
+ c->hash[0] = 0x67452301;
+ c->hash[1] = 0xefcdab89;
+ c->hash[2] = 0x98badcfe;
+ c->hash[3] = 0x10325476;
+ c->hash[4] = 0xc3d2e1f0;
+}
+
+void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n)
+{
+ uint32_t workspace[80];
+ unsigned int partial;
+ unsigned long done;
+
+ partial = c->len & 0x3f;
+ c->len += n;
+ if ((partial + n) >= 64) {
+ if (partial) {
+ done = 64 - partial;
+ memcpy(c->buffer + partial, p, done);
+ sha_transform(c->hash, c->buffer, workspace);
+ partial = 0;
+ } else
+ done = 0;
+ while (n >= done + 64) {
+ sha_transform(c->hash, p + done, workspace);
+ done += 64;
+ }
+ } else
+ done = 0;
+ if (n - done)
+ memcpy(c->buffer + partial, p + done, n - done);
+}
+
+void SHA1_Final(unsigned char *hash, SHA_CTX *c)
+{
+ uint64_t bitlen;
+ uint32_t bitlen_hi, bitlen_lo;
+ unsigned int i, offset, padlen;
+ unsigned char bits[8];
+ static const unsigned char padding[64] = { 0x80, };
+
+ bitlen = c->len << 3;
+ offset = c->len & 0x3f;
+ padlen = ((offset < 56) ? 56 : (64 + 56)) - offset;
+ SHA1_Update(c, padding, padlen);
+
+ bitlen_hi = bitlen >> 32;
+ bitlen_lo = bitlen & 0xffffffff;
+ bits[0] = bitlen_hi >> 24;
+ bits[1] = bitlen_hi >> 16;
+ bits[2] = bitlen_hi >> 8;
+ bits[3] = bitlen_hi;
+ bits[4] = bitlen_lo >> 24;
+ bits[5] = bitlen_lo >> 16;
+ bits[6] = bitlen_lo >> 8;
+ bits[7] = bitlen_lo;
+ SHA1_Update(c, bits, 8);
+
+ for (i = 0; i < 5; i++) {
+ uint32_t v = c->hash[i];
+ hash[0] = v >> 24;
+ hash[1] = v >> 16;
+ hash[2] = v >> 8;
+ hash[3] = v;
+ hash += 4;
+ }
+}
diff --git a/arm/sha1.h b/arm/sha1.h
new file mode 100644
index 000000000..395264634
--- /dev/null
+++ b/arm/sha1.h
@@ -0,0 +1,18 @@
+/*
+ * SHA-1 implementation optimized for ARM
+ *
+ * Copyright: (C) 2005 by Nicolas Pitre <nico@cam.org>
+ * Created: September 17, 2005
+ */
+
+#include <stdint.h>
+
+typedef struct sha_context {
+ uint64_t len;
+ uint32_t hash[5];
+ unsigned char buffer[64];
+} SHA_CTX;
+
+void SHA1_Init(SHA_CTX *c);
+void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n);
+void SHA1_Final(unsigned char *hash, SHA_CTX *c);
diff --git a/arm/sha1_arm.S b/arm/sha1_arm.S
new file mode 100644
index 000000000..da92d20e8
--- /dev/null
+++ b/arm/sha1_arm.S
@@ -0,0 +1,184 @@
+/*
+ * SHA transform optimized for ARM
+ *
+ * Copyright: (C) 2005 by Nicolas Pitre <nico@cam.org>
+ * Created: September 17, 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+ .text
+ .globl sha_transform
+
+/*
+ * void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W);
+ *
+ * note: the "data" pointer may be unaligned.
+ */
+
+sha_transform:
+
+ stmfd sp!, {r4 - r8, lr}
+
+ @ for (i = 0; i < 16; i++)
+ @ W[i] = ntohl(((uint32_t *)data)[i]); */
+
+#ifdef __ARMEB__
+ mov r4, r0
+ mov r0, r2
+ mov r2, #64
+ bl memcpy
+ mov r2, r0
+ mov r0, r4
+#else
+ mov r3, r2
+ mov lr, #16
+1: ldrb r4, [r1], #1
+ ldrb r5, [r1], #1
+ ldrb r6, [r1], #1
+ ldrb r7, [r1], #1
+ subs lr, lr, #1
+ orr r5, r5, r4, lsl #8
+ orr r6, r6, r5, lsl #8
+ orr r7, r7, r6, lsl #8
+ str r7, [r3], #4
+ bne 1b
+#endif
+
+ @ for (i = 0; i < 64; i++)
+ @ W[i+16] = ror(W[i+13] ^ W[i+8] ^ W[i+2] ^ W[i], 31);
+
+ sub r3, r2, #4
+ mov lr, #64
+2: ldr r4, [r3, #4]!
+ subs lr, lr, #1
+ ldr r5, [r3, #8]
+ ldr r6, [r3, #32]
+ ldr r7, [r3, #52]
+ eor r4, r4, r5
+ eor r4, r4, r6
+ eor r4, r4, r7
+ mov r4, r4, ror #31
+ str r4, [r3, #64]
+ bne 2b
+
+ /*
+ * The SHA functions are:
+ *
+ * f1(B,C,D) = (D ^ (B & (C ^ D)))
+ * f2(B,C,D) = (B ^ C ^ D)
+ * f3(B,C,D) = ((B & C) | (D & (B | C)))
+ *
+ * Then the sub-blocks are processed as follows:
+ *
+ * A' = ror(A, 27) + f(B,C,D) + E + K + *W++
+ * B' = A
+ * C' = ror(B, 2)
+ * D' = C
+ * E' = D
+ *
+ * We therefore unroll each loop 5 times to avoid register shuffling.
+ * Also the ror for C (and also D and E which are successivelyderived
+ * from it) is applied in place to cut on an additional mov insn for
+ * each round.
+ */
+
+ .macro sha_f1, A, B, C, D, E
+ ldr r3, [r2], #4
+ eor ip, \C, \D
+ add \E, r1, \E, ror #2
+ and ip, \B, ip, ror #2
+ add \E, \E, \A, ror #27
+ eor ip, ip, \D, ror #2
+ add \E, \E, r3
+ add \E, \E, ip
+ .endm
+
+ .macro sha_f2, A, B, C, D, E
+ ldr r3, [r2], #4
+ add \E, r1, \E, ror #2
+ eor ip, \B, \C, ror #2
+ add \E, \E, \A, ror #27
+ eor ip, ip, \D, ror #2
+ add \E, \E, r3
+ add \E, \E, ip
+ .endm
+
+ .macro sha_f3, A, B, C, D, E
+ ldr r3, [r2], #4
+ add \E, r1, \E, ror #2
+ orr ip, \B, \C, ror #2
+ add \E, \E, \A, ror #27
+ and ip, ip, \D, ror #2
+ add \E, \E, r3
+ and r3, \B, \C, ror #2
+ orr ip, ip, r3
+ add \E, \E, ip
+ .endm
+
+ ldmia r0, {r4 - r8}
+
+ mov lr, #4
+ ldr r1, .L_sha_K + 0
+
+ /* adjust initial values */
+ mov r6, r6, ror #30
+ mov r7, r7, ror #30
+ mov r8, r8, ror #30
+
+3: subs lr, lr, #1
+ sha_f1 r4, r5, r6, r7, r8
+ sha_f1 r8, r4, r5, r6, r7
+ sha_f1 r7, r8, r4, r5, r6
+ sha_f1 r6, r7, r8, r4, r5
+ sha_f1 r5, r6, r7, r8, r4
+ bne 3b
+
+ ldr r1, .L_sha_K + 4
+ mov lr, #4
+
+4: subs lr, lr, #1
+ sha_f2 r4, r5, r6, r7, r8
+ sha_f2 r8, r4, r5, r6, r7
+ sha_f2 r7, r8, r4, r5, r6
+ sha_f2 r6, r7, r8, r4, r5
+ sha_f2 r5, r6, r7, r8, r4
+ bne 4b
+
+ ldr r1, .L_sha_K + 8
+ mov lr, #4
+
+5: subs lr, lr, #1
+ sha_f3 r4, r5, r6, r7, r8
+ sha_f3 r8, r4, r5, r6, r7
+ sha_f3 r7, r8, r4, r5, r6
+ sha_f3 r6, r7, r8, r4, r5
+ sha_f3 r5, r6, r7, r8, r4
+ bne 5b
+
+ ldr r1, .L_sha_K + 12
+ mov lr, #4
+
+6: subs lr, lr, #1
+ sha_f2 r4, r5, r6, r7, r8
+ sha_f2 r8, r4, r5, r6, r7
+ sha_f2 r7, r8, r4, r5, r6
+ sha_f2 r6, r7, r8, r4, r5
+ sha_f2 r5, r6, r7, r8, r4
+ bne 6b
+
+ ldmia r0, {r1, r2, r3, ip, lr}
+ add r4, r1, r4
+ add r5, r2, r5
+ add r6, r3, r6, ror #2
+ add r7, ip, r7, ror #2
+ add r8, lr, r8, ror #2
+ stmia r0, {r4 - r8}
+
+ ldmfd sp!, {r4 - r8, pc}
+
+.L_sha_K:
+ .word 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6
+
diff --git a/blob.c b/blob.c
new file mode 100644
index 000000000..ea52ad5c9
--- /dev/null
+++ b/blob.c
@@ -0,0 +1,52 @@
+#include "blob.h"
+#include "cache.h"
+#include <stdlib.h>
+
+const char *blob_type = "blob";
+
+struct blob *lookup_blob(const unsigned char *sha1)
+{
+ struct object *obj = lookup_object(sha1);
+ if (!obj) {
+ struct blob *ret = xmalloc(sizeof(struct blob));
+ memset(ret, 0, sizeof(struct blob));
+ created_object(sha1, &ret->object);
+ ret->object.type = blob_type;
+ return ret;
+ }
+ if (!obj->type)
+ obj->type = blob_type;
+ if (obj->type != blob_type) {
+ error("Object %s is a %s, not a blob",
+ sha1_to_hex(sha1), obj->type);
+ return NULL;
+ }
+ return (struct blob *) obj;
+}
+
+int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size)
+{
+ item->object.parsed = 1;
+ return 0;
+}
+
+int parse_blob(struct blob *item)
+{
+ char type[20];
+ void *buffer;
+ unsigned long size;
+ int ret;
+
+ if (item->object.parsed)
+ return 0;
+ buffer = read_sha1_file(item->object.sha1, type, &size);
+ if (!buffer)
+ return error("Could not read %s",
+ sha1_to_hex(item->object.sha1));
+ if (strcmp(type, blob_type))
+ return error("Object %s not a blob",
+ sha1_to_hex(item->object.sha1));
+ ret = parse_blob_buffer(item, buffer, size);
+ free(buffer);
+ return ret;
+}
diff --git a/blob.h b/blob.h
new file mode 100644
index 000000000..ea5d9e9f8
--- /dev/null
+++ b/blob.h
@@ -0,0 +1,18 @@
+#ifndef BLOB_H
+#define BLOB_H
+
+#include "object.h"
+
+extern const char *blob_type;
+
+struct blob {
+ struct object object;
+};
+
+struct blob *lookup_blob(const unsigned char *sha1);
+
+int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size);
+
+int parse_blob(struct blob *item);
+
+#endif /* BLOB_H */
diff --git a/cache.h b/cache.h
new file mode 100644
index 000000000..99afa2c3c
--- /dev/null
+++ b/cache.h
@@ -0,0 +1,421 @@
+#ifndef CACHE_H
+#define CACHE_H
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#ifndef NO_MMAP
+#include <sys/mman.h>
+#endif
+#include <sys/param.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include SHA1_HEADER
+#include <zlib.h>
+
+#if ZLIB_VERNUM < 0x1200
+#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11)
+#endif
+
+#ifdef DT_UNKNOWN
+#define DTYPE(de) ((de)->d_type)
+#else
+#define DT_UNKNOWN 0
+#define DT_DIR 1
+#define DT_REG 2
+#define DT_LNK 3
+#define DTYPE(de) DT_UNKNOWN
+#endif
+
+#ifdef __GNUC__
+#define NORETURN __attribute__((__noreturn__))
+#else
+#define NORETURN
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+/*
+ * Intensive research over the course of many years has shown that
+ * port 9418 is totally unused by anything else. Or
+ *
+ * Your search - "port 9418" - did not match any documents.
+ *
+ * as www.google.com puts it.
+ *
+ * This port has been properly assigned for git use by IANA:
+ * git (Assigned-9418) [I06-050728-0001].
+ *
+ * git 9418/tcp git pack transfer service
+ * git 9418/udp git pack transfer service
+ *
+ * with Linus Torvalds <torvalds@osdl.org> as the point of
+ * contact. September 2005.
+ *
+ * See http://www.iana.org/assignments/port-numbers
+ */
+#define DEFAULT_GIT_PORT 9418
+
+/*
+ * Basic data structures for the directory cache
+ */
+
+#define CACHE_SIGNATURE 0x44495243 /* "DIRC" */
+struct cache_header {
+ unsigned int hdr_signature;
+ unsigned int hdr_version;
+ unsigned int hdr_entries;
+};
+
+/*
+ * The "cache_time" is just the low 32 bits of the
+ * time. It doesn't matter if it overflows - we only
+ * check it for equality in the 32 bits we save.
+ */
+struct cache_time {
+ unsigned int sec;
+ unsigned int nsec;
+};
+
+/*
+ * dev/ino/uid/gid/size are also just tracked to the low 32 bits
+ * Again - this is just a (very strong in practice) heuristic that
+ * the inode hasn't changed.
+ *
+ * We save the fields in big-endian order to allow using the
+ * index file over NFS transparently.
+ */
+struct cache_entry {
+ struct cache_time ce_ctime;
+ struct cache_time ce_mtime;
+ unsigned int ce_dev;
+ unsigned int ce_ino;
+ unsigned int ce_mode;
+ unsigned int ce_uid;
+ unsigned int ce_gid;
+ unsigned int ce_size;
+ unsigned char sha1[20];
+ unsigned short ce_flags;
+ char name[0];
+};
+
+#define CE_NAMEMASK (0x0fff)
+#define CE_STAGEMASK (0x3000)
+#define CE_UPDATE (0x4000)
+#define CE_STAGESHIFT 12
+
+#define create_ce_flags(len, stage) htons((len) | ((stage) << CE_STAGESHIFT))
+#define ce_namelen(ce) (CE_NAMEMASK & ntohs((ce)->ce_flags))
+#define ce_size(ce) cache_entry_size(ce_namelen(ce))
+#define ce_stage(ce) ((CE_STAGEMASK & ntohs((ce)->ce_flags)) >> CE_STAGESHIFT)
+
+#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644)
+static inline unsigned int create_ce_mode(unsigned int mode)
+{
+ if (S_ISLNK(mode))
+ return htonl(S_IFLNK);
+ return htonl(S_IFREG | ce_permissions(mode));
+}
+
+#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
+
+extern struct cache_entry **active_cache;
+extern unsigned int active_nr, active_alloc, active_cache_changed;
+
+#define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
+#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
+#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
+#define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
+
+extern char *get_git_dir(void);
+extern char *get_object_directory(void);
+extern char *get_refs_directory(void);
+extern char *get_index_file(void);
+extern char *get_graft_file(void);
+
+#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
+
+extern const char **get_pathspec(const char *prefix, const char **pathspec);
+extern const char *setup_git_directory(void);
+extern const char *prefix_path(const char *prefix, int len, const char *path);
+
+#define alloc_nr(x) (((x)+16)*3/2)
+
+/* Initialize and use the cache information */
+extern int read_cache(void);
+extern int write_cache(int newfd, struct cache_entry **cache, int entries);
+extern int cache_name_pos(const char *name, int namelen);
+#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */
+#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */
+#define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */
+extern int add_cache_entry(struct cache_entry *ce, int option);
+extern int remove_cache_entry_at(int pos);
+extern int remove_file_from_cache(const char *path);
+extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
+extern int ce_match_stat(struct cache_entry *ce, struct stat *st);
+extern int ce_modified(struct cache_entry *ce, struct stat *st);
+extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
+extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type);
+extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
+extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
+
+struct cache_file {
+ struct cache_file *next;
+ char lockfile[PATH_MAX];
+};
+extern int hold_index_file_for_update(struct cache_file *, const char *path);
+extern int commit_index_file(struct cache_file *);
+extern void rollback_index_file(struct cache_file *);
+
+extern int trust_executable_bit;
+extern int only_use_symrefs;
+extern int diff_rename_limit_default;
+
+#define MTIME_CHANGED 0x0001
+#define CTIME_CHANGED 0x0002
+#define OWNER_CHANGED 0x0004
+#define MODE_CHANGED 0x0008
+#define INODE_CHANGED 0x0010
+#define DATA_CHANGED 0x0020
+#define TYPE_CHANGED 0x0040
+
+/* Return a statically allocated filename matching the sha1 signature */
+extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern char *sha1_file_name(const unsigned char *sha1);
+extern char *sha1_pack_name(const unsigned char *sha1);
+extern char *sha1_pack_index_name(const unsigned char *sha1);
+extern const char *find_unique_abbrev(const unsigned char *sha1, int);
+extern const unsigned char null_sha1[20];
+
+int git_mkstemp(char *path, size_t n, const char *template);
+
+int safe_create_leading_directories(char *path);
+char *safe_strncpy(char *, const char *, size_t);
+
+/* Read and unpack a sha1 file into memory, write memory to a sha1 file */
+extern int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size);
+extern int parse_sha1_header(char *hdr, char *type, unsigned long *sizep);
+extern int sha1_object_info(const unsigned char *, char *, unsigned long *);
+extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size);
+extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
+extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
+extern char *write_sha1_file_prepare(void *buf,
+ unsigned long len,
+ const char *type,
+ unsigned char *sha1,
+ unsigned char *hdr,
+ int *hdrlen);
+
+extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
+
+/* Read a tree into the cache */
+extern int read_tree(void *buffer, unsigned long size, int stage, const char **paths);
+
+extern int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
+ size_t bufsize, size_t *bufposn);
+extern int write_sha1_to_fd(int fd, const unsigned char *sha1);
+extern int move_temp_to_file(const char *tmpfile, char *filename);
+
+extern int has_sha1_pack(const unsigned char *sha1);
+extern int has_sha1_file(const unsigned char *sha1);
+
+extern int has_pack_file(const unsigned char *sha1);
+extern int has_pack_index(const unsigned char *sha1);
+
+/* Convert to/from hex/sha1 representation */
+extern int get_sha1(const char *str, unsigned char *sha1);
+extern int get_sha1_hex(const char *hex, unsigned char *sha1);
+extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */
+extern int read_ref(const char *filename, unsigned char *sha1);
+extern const char *resolve_ref(const char *path, unsigned char *sha1, int);
+extern int create_symref(const char *git_HEAD, const char *refs_heads_master);
+extern int validate_symref(const char *git_HEAD);
+
+/* General helper functions */
+extern void usage(const char *err) NORETURN;
+extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
+extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+
+extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
+extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
+
+extern void *read_object_with_reference(const unsigned char *sha1,
+ const char *required_type,
+ unsigned long *size,
+ unsigned char *sha1_ret);
+
+const char *show_date(unsigned long time, int timezone);
+int parse_date(const char *date, char *buf, int bufsize);
+void datestamp(char *buf, int bufsize);
+unsigned long approxidate(const char *);
+
+extern int setup_ident(void);
+extern char *get_ident(const char *name, const char *email, const char *date_str);
+extern char *git_author_info(void);
+extern char *git_committer_info(void);
+
+static inline void *xmalloc(size_t size)
+{
+ void *ret = malloc(size);
+ if (!ret)
+ die("Out of memory, malloc failed");
+ return ret;
+}
+
+static inline void *xrealloc(void *ptr, size_t size)
+{
+ void *ret = realloc(ptr, size);
+ if (!ret)
+ die("Out of memory, realloc failed");
+ return ret;
+}
+
+static inline void *xcalloc(size_t nmemb, size_t size)
+{
+ void *ret = calloc(nmemb, size);
+ if (!ret)
+ die("Out of memory, calloc failed");
+ return ret;
+}
+
+struct checkout {
+ const char *base_dir;
+ int base_dir_len;
+ unsigned force:1,
+ quiet:1,
+ not_new:1,
+ refresh_cache:1;
+};
+
+extern int checkout_entry(struct cache_entry *ce, struct checkout *state);
+
+extern struct alternate_object_database {
+ struct alternate_object_database *next;
+ char *name;
+ char base[0]; /* more */
+} *alt_odb_list;
+extern void prepare_alt_odb(void);
+
+extern struct packed_git {
+ struct packed_git *next;
+ unsigned long index_size;
+ unsigned long pack_size;
+ unsigned int *index_base;
+ void *pack_base;
+ unsigned int pack_last_used;
+ unsigned int pack_use_cnt;
+ int pack_local;
+ unsigned char sha1[20];
+ char pack_name[0]; /* something like ".git/objects/pack/xxxxx.pack" */
+} *packed_git;
+
+struct pack_entry {
+ unsigned int offset;
+ unsigned char sha1[20];
+ struct packed_git *p;
+};
+
+struct ref {
+ struct ref *next;
+ unsigned char old_sha1[20];
+ unsigned char new_sha1[20];
+ unsigned char force;
+ struct ref *peer_ref; /* when renaming */
+ char name[0];
+};
+
+extern int git_connect(int fd[2], char *url, const char *prog);
+extern int finish_connect(pid_t pid);
+extern int path_match(const char *path, int nr, char **match);
+extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+ int nr_refspec, char **refspec, int all);
+extern int get_ack(int fd, unsigned char *result_sha1);
+extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, int ignore_funny);
+extern int server_supports(const char *feature);
+
+extern struct packed_git *parse_pack_index(unsigned char *sha1);
+extern struct packed_git *parse_pack_index_file(const unsigned char *sha1,
+ char *idx_path);
+
+extern void prepare_packed_git(void);
+extern void install_packed_git(struct packed_git *pack);
+
+extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
+ struct packed_git *packs);
+
+extern int use_packed_git(struct packed_git *);
+extern void unuse_packed_git(struct packed_git *);
+extern struct packed_git *add_packed_git(char *, int, int);
+extern int num_packed_objects(const struct packed_git *p);
+extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
+extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *);
+extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *);
+extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, int *, unsigned char *);
+
+/* Dumb servers support */
+extern int update_server_info(int);
+
+#ifdef NO_MMAP
+
+#ifndef PROT_READ
+#define PROT_READ 1
+#define PROT_WRITE 2
+#define MAP_PRIVATE 1
+#define MAP_FAILED ((void*)-1)
+#endif
+
+extern void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
+extern int gitfakemunmap(void *start, size_t length);
+
+#endif
+
+typedef int (*config_fn_t)(const char *, const char *);
+extern int git_default_config(const char *, const char *);
+extern int git_config(config_fn_t fn);
+extern int git_config_int(const char *, const char *);
+extern int git_config_bool(const char *, const char *);
+
+#define MAX_GITNAME (1000)
+extern char git_default_email[MAX_GITNAME];
+extern char git_default_name[MAX_GITNAME];
+
+/* Sane ctype - no locale, and works with signed chars */
+#undef isspace
+#undef isdigit
+#undef isalpha
+#undef isalnum
+#undef tolower
+#undef toupper
+extern unsigned char sane_ctype[256];
+#define GIT_SPACE 0x01
+#define GIT_DIGIT 0x02
+#define GIT_ALPHA 0x04
+#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
+#define isspace(x) sane_istest(x,GIT_SPACE)
+#define isdigit(x) sane_istest(x,GIT_DIGIT)
+#define isalpha(x) sane_istest(x,GIT_ALPHA)
+#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define tolower(x) sane_case((unsigned char)(x), 0x20)
+#define toupper(x) sane_case((unsigned char)(x), 0)
+
+static inline int sane_case(int x, int high)
+{
+ if (sane_istest(x, GIT_ALPHA))
+ x = (x & ~0x20) | high;
+ return x;
+}
+
+extern int copy_fd(int ifd, int ofd);
+#endif /* CACHE_H */
diff --git a/cat-file.c b/cat-file.c
new file mode 100644
index 000000000..d775a1545
--- /dev/null
+++ b/cat-file.c
@@ -0,0 +1,56 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+ unsigned char sha1[20];
+ char type[20];
+ void *buf;
+ unsigned long size;
+
+ setup_git_directory();
+ if (argc != 3 || get_sha1(argv[2], sha1))
+ usage("git-cat-file [-t | -s | <type>] <sha1>");
+
+ if (!strcmp("-t", argv[1]) || !strcmp("-s", argv[1])) {
+ if (!sha1_object_info(sha1, type,
+ argv[1][1] == 's' ? &size : NULL)) {
+ switch (argv[1][1]) {
+ case 't':
+ printf("%s\n", type);
+ break;
+ case 's':
+ printf("%lu\n", size);
+ break;
+ }
+ return 0;
+ }
+ buf = NULL;
+ } else {
+ buf = read_object_with_reference(sha1, argv[1], &size, NULL);
+ }
+
+ if (!buf)
+ die("git-cat-file %s: bad file", argv[2]);
+
+ while (size > 0) {
+ long ret = write(1, buf, size);
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ continue;
+ /* Ignore epipe */
+ if (errno == EPIPE)
+ break;
+ die("git-cat-file: %s", strerror(errno));
+ } else if (!ret) {
+ die("git-cat-file: disk full?");
+ }
+ size -= ret;
+ buf += ret;
+ }
+ return 0;
+}
diff --git a/check-ref-format.c b/check-ref-format.c
new file mode 100644
index 000000000..a0adb3dcb
--- /dev/null
+++ b/check-ref-format.c
@@ -0,0 +1,17 @@
+/*
+ * GIT - The information manager from hell
+ */
+
+#include "cache.h"
+#include "refs.h"
+
+#include <stdio.h>
+
+int main(int ac, char **av)
+{
+ if (ac != 2)
+ usage("git-check-ref-format refname");
+ if (check_ref_format(av[1]))
+ exit(1);
+ return 0;
+}
diff --git a/checkout-index.c b/checkout-index.c
new file mode 100644
index 000000000..dab3778a9
--- /dev/null
+++ b/checkout-index.c
@@ -0,0 +1,169 @@
+/*
+ * Check-out files from the "current cache directory"
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ *
+ * Careful: order of argument flags does matter. For example,
+ *
+ * git-checkout-index -a -f file.c
+ *
+ * Will first check out all files listed in the cache (but not
+ * overwrite any old ones), and then force-checkout "file.c" a
+ * second time (ie that one _will_ overwrite any old contents
+ * with the same filename).
+ *
+ * Also, just doing "git-checkout-index" does nothing. You probably
+ * meant "git-checkout-index -a". And if you want to force it, you
+ * want "git-checkout-index -f -a".
+ *
+ * Intuitiveness is not the goal here. Repeatability is. The
+ * reason for the "no arguments means no work" thing is that
+ * from scripts you are supposed to be able to do things like
+ *
+ * find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
+ *
+ * which will force all existing *.h files to be replaced with
+ * their cached copies. If an empty command line implied "all",
+ * then this would force-refresh everything in the cache, which
+ * was not the point.
+ *
+ * Oh, and the "--" is just a good idea when you know the rest
+ * will be filenames. Just so that you wouldn't have a filename
+ * of "-a" causing problems (not possible in the above example,
+ * but get used to it in scripting!).
+ */
+#include "cache.h"
+
+static struct checkout state = {
+ .base_dir = "",
+ .base_dir_len = 0,
+ .force = 0,
+ .quiet = 0,
+ .not_new = 0,
+ .refresh_cache = 0,
+};
+
+static int checkout_file(const char *name)
+{
+ int pos = cache_name_pos(name, strlen(name));
+ if (pos < 0) {
+ if (!state.quiet) {
+ pos = -pos - 1;
+ fprintf(stderr,
+ "git-checkout-index: %s is %s.\n",
+ name,
+ (pos < active_nr &&
+ !strcmp(active_cache[pos]->name, name)) ?
+ "unmerged" : "not in the cache");
+ }
+ return -1;
+ }
+ return checkout_entry(active_cache[pos], &state);
+}
+
+static int checkout_all(void)
+{
+ int i, errs = 0;
+
+ for (i = 0; i < active_nr ; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ce_stage(ce))
+ continue;
+ if (checkout_entry(ce, &state) < 0)
+ errs++;
+ }
+ if (errs)
+ /* we have already done our error reporting.
+ * exit with the same code as die().
+ */
+ exit(128);
+ return 0;
+}
+
+static const char checkout_cache_usage[] =
+"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--prefix=<string>] [--] <file>...";
+
+static struct cache_file cache_file;
+
+int main(int argc, char **argv)
+{
+ int i;
+ int newfd = -1;
+ int all = 0;
+
+ if (read_cache() < 0) {
+ die("invalid cache");
+ }
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(arg, "-a") || !strcmp(arg, "--all")) {
+ all = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-f") || !strcmp(arg, "--force")) {
+ state.force = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
+ state.quiet = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-n") || !strcmp(arg, "--no-create")) {
+ state.not_new = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) {
+ state.refresh_cache = 1;
+ if (newfd < 0)
+ newfd = hold_index_file_for_update
+ (&cache_file,
+ get_index_file());
+ if (newfd < 0)
+ die("cannot open index.lock file.");
+ continue;
+ }
+ if (!memcmp(arg, "--prefix=", 9)) {
+ state.base_dir = arg+9;
+ state.base_dir_len = strlen(state.base_dir);
+ continue;
+ }
+ if (arg[0] == '-')
+ usage(checkout_cache_usage);
+ break;
+ }
+
+ if (state.base_dir_len) {
+ /* when --prefix is specified we do not
+ * want to update cache.
+ */
+ if (state.refresh_cache) {
+ close(newfd); newfd = -1;
+ rollback_index_file(&cache_file);
+ }
+ state.refresh_cache = 0;
+ }
+
+ /* Check out named files first */
+ for ( ; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (all)
+ die("git-checkout-index: don't mix '--all' and explicit filenames");
+ checkout_file(arg);
+ }
+
+ if (all)
+ checkout_all();
+
+ if (0 <= newfd &&
+ (write_cache(newfd, active_cache, active_nr) ||
+ commit_index_file(&cache_file)))
+ die("Unable to write new cachefile");
+ return 0;
+}
diff --git a/clone-pack.c b/clone-pack.c
new file mode 100644
index 000000000..960921903
--- /dev/null
+++ b/clone-pack.c
@@ -0,0 +1,305 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static const char clone_pack_usage[] =
+"git-clone-pack [--exec=<git-upload-pack>] [<host>:]<directory> [<heads>]*";
+static const char *exec = "git-upload-pack";
+
+static void clone_handshake(int fd[2], struct ref *ref)
+{
+ unsigned char sha1[20];
+
+ while (ref) {
+ packet_write(fd[1], "want %s\n", sha1_to_hex(ref->old_sha1));
+ ref = ref->next;
+ }
+ packet_flush(fd[1]);
+
+ /* We don't have nuttin' */
+ packet_write(fd[1], "done\n");
+ if (get_ack(fd[0], sha1))
+ error("Huh! git-clone-pack got positive ack for %s", sha1_to_hex(sha1));
+}
+
+static int is_master(struct ref *ref)
+{
+ return !strcmp(ref->name, "refs/heads/master");
+}
+
+static void write_one_ref(struct ref *ref)
+{
+ char *path = git_path("%s", ref->name);
+ int fd;
+ char *hex;
+
+ if (!strncmp(ref->name, "refs/", 5) &&
+ check_ref_format(ref->name + 5)) {
+ error("refusing to create funny ref '%s' locally", ref->name);
+ return;
+ }
+
+ if (safe_create_leading_directories(path))
+ die("unable to create leading directory for %s", ref->name);
+ fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ if (fd < 0)
+ die("unable to create ref %s", ref->name);
+ hex = sha1_to_hex(ref->old_sha1);
+ hex[40] = '\n';
+ if (write(fd, hex, 41) != 41)
+ die("unable to write ref %s", ref->name);
+ close(fd);
+}
+
+static void write_refs(struct ref *ref)
+{
+ struct ref *head = NULL, *head_ptr, *master_ref;
+ char *head_path;
+
+ /* Upload-pack must report HEAD first */
+ if (!strcmp(ref->name, "HEAD")) {
+ head = ref;
+ ref = ref->next;
+ }
+ head_ptr = NULL;
+ master_ref = NULL;
+ while (ref) {
+ if (is_master(ref))
+ master_ref = ref;
+ if (head &&
+ !memcmp(ref->old_sha1, head->old_sha1, 20) &&
+ !strncmp(ref->name, "refs/heads/",11) &&
+ (!head_ptr || ref == master_ref))
+ head_ptr = ref;
+
+ write_one_ref(ref);
+ ref = ref->next;
+ }
+ if (!head) {
+ fprintf(stderr, "No HEAD in remote.\n");
+ return;
+ }
+
+ head_path = strdup(git_path("HEAD"));
+ if (!head_ptr) {
+ /*
+ * If we had a master ref, and it wasn't HEAD, we need to undo the
+ * symlink, and write a standalone HEAD. Give a warning, because that's
+ * really really wrong.
+ */
+ if (master_ref) {
+ error("HEAD doesn't point to any refs! Making standalone HEAD");
+ unlink(head_path);
+ }
+ write_one_ref(head);
+ free(head_path);
+ return;
+ }
+
+ /* We reset to the master branch if it's available */
+ if (master_ref)
+ return;
+
+ fprintf(stderr, "Setting HEAD to %s\n", head_ptr->name);
+
+ /*
+ * Uhhuh. Other end didn't have master. We start HEAD off with
+ * the first branch with the same value.
+ */
+ if (create_symref(head_path, head_ptr->name) < 0)
+ die("unable to link HEAD to %s", head_ptr->name);
+ free(head_path);
+}
+
+static int finish_pack(const char *pack_tmp_name)
+{
+ int pipe_fd[2];
+ pid_t pid;
+ char idx[PATH_MAX];
+ char final[PATH_MAX];
+ char hash[41];
+ unsigned char sha1[20];
+ char *cp;
+ int err = 0;
+
+ if (pipe(pipe_fd) < 0)
+ die("git-clone-pack: unable to set up pipe");
+
+ strcpy(idx, pack_tmp_name); /* ".git/objects/pack-XXXXXX" */
+ cp = strrchr(idx, '/');
+ memcpy(cp, "/pidx", 5);
+
+ pid = fork();
+ if (pid < 0)
+ die("git-clone-pack: unable to fork off git-index-pack");
+ if (!pid) {
+ close(0);
+ dup2(pipe_fd[1], 1);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ execlp("git-index-pack","git-index-pack",
+ "-o", idx, pack_tmp_name, NULL);
+ error("cannot exec git-index-pack <%s> <%s>",
+ idx, pack_tmp_name);
+ exit(1);
+ }
+ close(pipe_fd[1]);
+ if (read(pipe_fd[0], hash, 40) != 40) {
+ error("git-clone-pack: unable to read from git-index-pack");
+ err = 1;
+ }
+ close(pipe_fd[0]);
+
+ for (;;) {
+ int status, code;
+ int retval = waitpid(pid, &status, 0);
+
+ if (retval < 0) {
+ if (errno == EINTR)
+ continue;
+ error("waitpid failed (%s)", strerror(retval));
+ goto error_die;
+ }
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+ error("git-index-pack died of signal %d", sig);
+ goto error_die;
+ }
+ if (!WIFEXITED(status)) {
+ error("git-index-pack died of unnatural causes %d",
+ status);
+ goto error_die;
+ }
+ code = WEXITSTATUS(status);
+ if (code) {
+ error("git-index-pack died with error code %d", code);
+ goto error_die;
+ }
+ if (err)
+ goto error_die;
+ break;
+ }
+ hash[40] = 0;
+ if (get_sha1_hex(hash, sha1)) {
+ error("git-index-pack reported nonsense '%s'", hash);
+ goto error_die;
+ }
+ /* Now we have pack in pack_tmp_name[], and
+ * idx in idx[]; rename them to their final names.
+ */
+ snprintf(final, sizeof(final),
+ "%s/pack/pack-%s.pack", get_object_directory(), hash);
+ move_temp_to_file(pack_tmp_name, final);
+ chmod(final, 0444);
+ snprintf(final, sizeof(final),
+ "%s/pack/pack-%s.idx", get_object_directory(), hash);
+ move_temp_to_file(idx, final);
+ chmod(final, 0444);
+ return 0;
+
+ error_die:
+ unlink(idx);
+ unlink(pack_tmp_name);
+ exit(1);
+}
+
+static int clone_without_unpack(int fd[2])
+{
+ char tmpfile[PATH_MAX];
+ int ofd, ifd;
+
+ ifd = fd[0];
+ snprintf(tmpfile, sizeof(tmpfile),
+ "%s/pack/tmp-XXXXXX", get_object_directory());
+ ofd = mkstemp(tmpfile);
+ if (ofd < 0)
+ return error("unable to create temporary file %s", tmpfile);
+
+ while (1) {
+ char buf[8192];
+ ssize_t sz, wsz, pos;
+ sz = read(ifd, buf, sizeof(buf));
+ if (sz == 0)
+ break;
+ if (sz < 0) {
+ error("error reading pack (%s)", strerror(errno));
+ close(ofd);
+ unlink(tmpfile);
+ return -1;
+ }
+ pos = 0;
+ while (pos < sz) {
+ wsz = write(ofd, buf + pos, sz - pos);
+ if (wsz < 0) {
+ error("error writing pack (%s)",
+ strerror(errno));
+ close(ofd);
+ unlink(tmpfile);
+ return -1;
+ }
+ pos += wsz;
+ }
+ }
+ close(ofd);
+ return finish_pack(tmpfile);
+}
+
+static int clone_pack(int fd[2], int nr_match, char **match)
+{
+ struct ref *refs;
+ int status;
+
+ get_remote_heads(fd[0], &refs, nr_match, match, 1);
+ if (!refs) {
+ packet_flush(fd[1]);
+ die("no matching remote head");
+ }
+ clone_handshake(fd, refs);
+
+ status = clone_without_unpack(fd);
+
+ if (!status)
+ write_refs(refs);
+ return status;
+}
+
+int main(int argc, char **argv)
+{
+ int i, ret, nr_heads;
+ char *dest = NULL, **heads;
+ int fd[2];
+ pid_t pid;
+
+ nr_heads = 0;
+ heads = NULL;
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strcmp("-q", arg))
+ continue;
+ if (!strncmp("--exec=", arg, 7)) {
+ exec = arg + 7;
+ continue;
+ }
+ if (!strcmp("--keep", arg))
+ continue;
+ usage(clone_pack_usage);
+ }
+ dest = arg;
+ heads = argv + i + 1;
+ nr_heads = argc - i - 1;
+ break;
+ }
+ if (!dest)
+ usage(clone_pack_usage);
+ pid = git_connect(fd, dest, exec);
+ if (pid < 0)
+ return 1;
+ ret = clone_pack(fd, nr_heads, heads);
+ close(fd[0]);
+ close(fd[1]);
+ finish_connect(pid);
+ return ret;
+}
diff --git a/cmd-rename.sh b/cmd-rename.sh
new file mode 100755
index 000000000..992493de2
--- /dev/null
+++ b/cmd-rename.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+#
+# If you installed git by hand previously, you may find this
+# script useful to remove the symbolic links that we shipped
+# for backward compatibility.
+#
+# Running this script with the previous installation directory
+# like this:
+#
+# $ cmd-rename.sh /usr/local/bin/
+#
+# would clean them.
+
+d="$1"
+test -d "$d" || exit
+while read old new
+do
+ rm -f "$d/$old"
+done <<\EOF
+git-add-script git-add
+git-archimport-script git-archimport
+git-bisect-script git-bisect
+git-branch-script git-branch
+git-checkout-script git-checkout
+git-cherry-pick-script git-cherry-pick
+git-clone-script git-clone
+git-commit-script git-commit
+git-count-objects-script git-count-objects
+git-cvsimport-script git-cvsimport
+git-diff-script git-diff
+git-send-email-script git-send-email
+git-fetch-script git-fetch
+git-format-patch-script git-format-patch
+git-log-script git-log
+git-ls-remote-script git-ls-remote
+git-merge-one-file-script git-merge-one-file
+git-octopus-script git-octopus
+git-parse-remote-script git-parse-remote
+git-prune-script git-prune
+git-pull-script git-pull
+git-push-script git-push
+git-rebase-script git-rebase
+git-relink-script git-relink
+git-rename-script git-rename
+git-repack-script git-repack
+git-request-pull-script git-request-pull
+git-reset-script git-reset
+git-resolve-script git-resolve
+git-revert-script git-revert
+git-sh-setup-script git-sh-setup
+git-status-script git-status
+git-tag-script git-tag
+git-verify-tag-script git-verify-tag
+git-http-pull git-http-fetch
+git-local-pull git-local-fetch
+git-checkout-cache git-checkout-index
+git-diff-cache git-diff-index
+git-merge-cache git-merge-index
+git-update-cache git-update-index
+git-convert-cache git-convert-objects
+git-fsck-cache git-fsck-objects
+EOF
diff --git a/commit-tree.c b/commit-tree.c
new file mode 100644
index 000000000..b60299fed
--- /dev/null
+++ b/commit-tree.c
@@ -0,0 +1,129 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+#define BLOCKING (1ul << 14)
+
+/*
+ * FIXME! Share the code with "write-tree.c"
+ */
+static void init_buffer(char **bufp, unsigned int *sizep)
+{
+ char *buf = xmalloc(BLOCKING);
+ *sizep = 0;
+ *bufp = buf;
+}
+
+static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
+{
+ char one_line[2048];
+ va_list args;
+ int len;
+ unsigned long alloc, size, newsize;
+ char *buf;
+
+ va_start(args, fmt);
+ len = vsnprintf(one_line, sizeof(one_line), fmt, args);
+ va_end(args);
+ size = *sizep;
+ newsize = size + len;
+ alloc = (size + 32767) & ~32767;
+ buf = *bufp;
+ if (newsize > alloc) {
+ alloc = (newsize + 32767) & ~32767;
+ buf = xrealloc(buf, alloc);
+ *bufp = buf;
+ }
+ *sizep = newsize;
+ memcpy(buf + size, one_line, len);
+}
+
+static void check_valid(unsigned char *sha1, const char *expect)
+{
+ void *buf;
+ char type[20];
+ unsigned long size;
+
+ buf = read_sha1_file(sha1, type, &size);
+ if (!buf || strcmp(type, expect))
+ die("%s is not a valid '%s' object", sha1_to_hex(sha1), expect);
+ free(buf);
+}
+
+/*
+ * Having more than two parents is not strange at all, and this is
+ * how multi-way merges are represented.
+ */
+#define MAXPARENT (16)
+static unsigned char parent_sha1[MAXPARENT][20];
+
+static const char commit_tree_usage[] = "git-commit-tree <sha1> [-p <sha1>]* < changelog";
+
+static int new_parent(int idx)
+{
+ int i;
+ unsigned char *sha1 = parent_sha1[idx];
+ for (i = 0; i < idx; i++) {
+ if (!memcmp(parent_sha1[i], sha1, 20)) {
+ error("duplicate parent %s ignored", sha1_to_hex(sha1));
+ return 0;
+ }
+ }
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ int parents = 0;
+ unsigned char tree_sha1[20];
+ unsigned char commit_sha1[20];
+ char comment[1000];
+ char *buffer;
+ unsigned int size;
+
+ setup_ident();
+ git_config(git_default_config);
+
+ if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
+ usage(commit_tree_usage);
+
+ check_valid(tree_sha1, "tree");
+ for (i = 2; i < argc; i += 2) {
+ char *a, *b;
+ a = argv[i]; b = argv[i+1];
+ if (!b || strcmp(a, "-p") || get_sha1(b, parent_sha1[parents]))
+ usage(commit_tree_usage);
+ check_valid(parent_sha1[parents], "commit");
+ if (new_parent(parents))
+ parents++;
+ }
+ if (!parents)
+ fprintf(stderr, "Committing initial tree %s\n", argv[1]);
+
+ init_buffer(&buffer, &size);
+ add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
+
+ /*
+ * NOTE! This ordering means that the same exact tree merged with a
+ * different order of parents will be a _different_ changeset even
+ * if everything else stays the same.
+ */
+ for (i = 0; i < parents; i++)
+ add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
+
+ /* Person/date information */
+ add_buffer(&buffer, &size, "author %s\n", git_author_info());
+ add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info());
+
+ /* And add the comment */
+ while (fgets(comment, sizeof(comment), stdin) != NULL)
+ add_buffer(&buffer, &size, "%s", comment);
+
+ write_sha1_file(buffer, size, "commit", commit_sha1);
+ printf("%s\n", sha1_to_hex(commit_sha1));
+ return 0;
+}
diff --git a/commit.c b/commit.c
new file mode 100644
index 000000000..e867b86e6
--- /dev/null
+++ b/commit.c
@@ -0,0 +1,637 @@
+#include "tag.h"
+#include "commit.h"
+#include "cache.h"
+
+int save_commit_buffer = 1;
+
+struct sort_node
+{
+ /*
+ * the number of children of the associated commit
+ * that also occur in the list being sorted.
+ */
+ unsigned int indegree;
+
+ /*
+ * reference to original list item that we will re-use
+ * on output.
+ */
+ struct commit_list * list_item;
+
+};
+
+const char *commit_type = "commit";
+
+enum cmit_fmt get_commit_format(const char *arg)
+{
+ if (!*arg)
+ return CMIT_FMT_DEFAULT;
+ if (!strcmp(arg, "=raw"))
+ return CMIT_FMT_RAW;
+ if (!strcmp(arg, "=medium"))
+ return CMIT_FMT_MEDIUM;
+ if (!strcmp(arg, "=short"))
+ return CMIT_FMT_SHORT;
+ if (!strcmp(arg, "=full"))
+ return CMIT_FMT_FULL;
+ if (!strcmp(arg, "=fuller"))
+ return CMIT_FMT_FULLER;
+ if (!strcmp(arg, "=oneline"))
+ return CMIT_FMT_ONELINE;
+ die("invalid --pretty format");
+}
+
+static struct commit *check_commit(struct object *obj,
+ const unsigned char *sha1,
+ int quiet)
+{
+ if (obj->type != commit_type) {
+ if (!quiet)
+ error("Object %s is a %s, not a commit",
+ sha1_to_hex(sha1), obj->type);
+ return NULL;
+ }
+ return (struct commit *) obj;
+}
+
+struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
+ int quiet)
+{
+ struct object *obj = deref_tag(parse_object(sha1), NULL, 0);
+
+ if (!obj)
+ return NULL;
+ return check_commit(obj, sha1, quiet);
+}
+
+struct commit *lookup_commit_reference(const unsigned char *sha1)
+{
+ return lookup_commit_reference_gently(sha1, 0);
+}
+
+struct commit *lookup_commit(const unsigned char *sha1)
+{
+ struct object *obj = lookup_object(sha1);
+ if (!obj) {
+ struct commit *ret = xmalloc(sizeof(struct commit));
+ memset(ret, 0, sizeof(struct commit));
+ created_object(sha1, &ret->object);
+ ret->object.type = commit_type;
+ return ret;
+ }
+ if (!obj->type)
+ obj->type = commit_type;
+ return check_commit(obj, sha1, 0);
+}
+
+static unsigned long parse_commit_date(const char *buf)
+{
+ unsigned long date;
+
+ if (memcmp(buf, "author", 6))
+ return 0;
+ while (*buf++ != '\n')
+ /* nada */;
+ if (memcmp(buf, "committer", 9))
+ return 0;
+ while (*buf++ != '>')
+ /* nada */;
+ date = strtoul(buf, NULL, 10);
+ if (date == ULONG_MAX)
+ date = 0;
+ return date;
+}
+
+static struct commit_graft {
+ unsigned char sha1[20];
+ int nr_parent;
+ unsigned char parent[0][20]; /* more */
+} **commit_graft;
+static int commit_graft_alloc, commit_graft_nr;
+
+static int commit_graft_pos(const unsigned char *sha1)
+{
+ int lo, hi;
+ lo = 0;
+ hi = commit_graft_nr;
+ while (lo < hi) {
+ int mi = (lo + hi) / 2;
+ struct commit_graft *graft = commit_graft[mi];
+ int cmp = memcmp(sha1, graft->sha1, 20);
+ if (!cmp)
+ return mi;
+ if (cmp < 0)
+ hi = mi;
+ else
+ lo = mi + 1;
+ }
+ return -lo - 1;
+}
+
+static void prepare_commit_graft(void)
+{
+ char *graft_file = get_graft_file();
+ FILE *fp = fopen(graft_file, "r");
+ char buf[1024];
+ if (!fp) {
+ commit_graft = (struct commit_graft **) "hack";
+ return;
+ }
+ while (fgets(buf, sizeof(buf), fp)) {
+ /* The format is just "Commit Parent1 Parent2 ...\n" */
+ int len = strlen(buf);
+ int i;
+ struct commit_graft *graft = NULL;
+
+ if (buf[len-1] == '\n')
+ buf[--len] = 0;
+ if (buf[0] == '#')
+ continue;
+ if ((len + 1) % 41) {
+ bad_graft_data:
+ error("bad graft data: %s", buf);
+ free(graft);
+ continue;
+ }
+ i = (len + 1) / 41 - 1;
+ graft = xmalloc(sizeof(*graft) + 20 * i);
+ graft->nr_parent = i;
+ if (get_sha1_hex(buf, graft->sha1))
+ goto bad_graft_data;
+ for (i = 40; i < len; i += 41) {
+ if (buf[i] != ' ')
+ goto bad_graft_data;
+ if (get_sha1_hex(buf + i + 1, graft->parent[i/41]))
+ goto bad_graft_data;
+ }
+ i = commit_graft_pos(graft->sha1);
+ if (0 <= i) {
+ error("duplicate graft data: %s", buf);
+ free(graft);
+ continue;
+ }
+ i = -i - 1;
+ if (commit_graft_alloc <= ++commit_graft_nr) {
+ commit_graft_alloc = alloc_nr(commit_graft_alloc);
+ commit_graft = xrealloc(commit_graft,
+ sizeof(*commit_graft) *
+ commit_graft_alloc);
+ }
+ if (i < commit_graft_nr)
+ memmove(commit_graft + i + 1,
+ commit_graft + i,
+ (commit_graft_nr - i - 1) *
+ sizeof(*commit_graft));
+ commit_graft[i] = graft;
+ }
+ fclose(fp);
+}
+
+static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
+{
+ int pos;
+ if (!commit_graft)
+ prepare_commit_graft();
+ pos = commit_graft_pos(sha1);
+ if (pos < 0)
+ return NULL;
+ return commit_graft[pos];
+}
+
+int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
+{
+ char *bufptr = buffer;
+ unsigned char parent[20];
+ struct commit_list **pptr;
+ struct commit_graft *graft;
+ unsigned n_refs = 0;
+
+ if (item->object.parsed)
+ return 0;
+ item->object.parsed = 1;
+ if (memcmp(bufptr, "tree ", 5))
+ return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
+ if (get_sha1_hex(bufptr + 5, parent) < 0)
+ return error("bad tree pointer in commit %s\n", sha1_to_hex(item->object.sha1));
+ item->tree = lookup_tree(parent);
+ if (item->tree)
+ n_refs++;
+ bufptr += 46; /* "tree " + "hex sha1" + "\n" */
+ pptr = &item->parents;
+
+ graft = lookup_commit_graft(item->object.sha1);
+ while (!memcmp(bufptr, "parent ", 7)) {
+ struct commit *new_parent;
+
+ if (get_sha1_hex(bufptr + 7, parent) || bufptr[47] != '\n')
+ return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
+ bufptr += 48;
+ if (graft)
+ continue;
+ new_parent = lookup_commit(parent);
+ if (new_parent) {
+ pptr = &commit_list_insert(new_parent, pptr)->next;
+ n_refs++;
+ }
+ }
+ if (graft) {
+ int i;
+ struct commit *new_parent;
+ for (i = 0; i < graft->nr_parent; i++) {
+ new_parent = lookup_commit(graft->parent[i]);
+ if (!new_parent)
+ continue;
+ pptr = &commit_list_insert(new_parent, pptr)->next;
+ n_refs++;
+ }
+ }
+ item->date = parse_commit_date(bufptr);
+
+ if (track_object_refs) {
+ unsigned i = 0;
+ struct commit_list *p;
+ struct object_refs *refs = alloc_object_refs(n_refs);
+ if (item->tree)
+ refs->ref[i++] = &item->tree->object;
+ for (p = item->parents; p; p = p->next)
+ refs->ref[i++] = &p->item->object;
+ set_object_refs(&item->object, refs);
+ }
+
+ return 0;
+}
+
+int parse_commit(struct commit *item)
+{
+ char type[20];
+ void *buffer;
+ unsigned long size;
+ int ret;
+
+ if (item->object.parsed)
+ return 0;
+ buffer = read_sha1_file(item->object.sha1, type, &size);
+ if (!buffer)
+ return error("Could not read %s",
+ sha1_to_hex(item->object.sha1));
+ if (strcmp(type, commit_type)) {
+ free(buffer);
+ return error("Object %s not a commit",
+ sha1_to_hex(item->object.sha1));
+ }
+ ret = parse_commit_buffer(item, buffer, size);
+ if (save_commit_buffer && !ret) {
+ item->buffer = buffer;
+ return 0;
+ }
+ free(buffer);
+ return ret;
+}
+
+struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
+{
+ struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
+ new_list->item = item;
+ new_list->next = *list_p;
+ *list_p = new_list;
+ return new_list;
+}
+
+void free_commit_list(struct commit_list *list)
+{
+ while (list) {
+ struct commit_list *temp = list;
+ list = temp->next;
+ free(temp);
+ }
+}
+
+struct commit_list * insert_by_date(struct commit *item, struct commit_list **list)
+{
+ struct commit_list **pp = list;
+ struct commit_list *p;
+ while ((p = *pp) != NULL) {
+ if (p->item->date < item->date) {
+ break;
+ }
+ pp = &p->next;
+ }
+ return commit_list_insert(item, pp);
+}
+
+
+void sort_by_date(struct commit_list **list)
+{
+ struct commit_list *ret = NULL;
+ while (*list) {
+ insert_by_date((*list)->item, &ret);
+ *list = (*list)->next;
+ }
+ *list = ret;
+}
+
+struct commit *pop_most_recent_commit(struct commit_list **list,
+ unsigned int mark)
+{
+ struct commit *ret = (*list)->item;
+ struct commit_list *parents = ret->parents;
+ struct commit_list *old = *list;
+
+ *list = (*list)->next;
+ free(old);
+
+ while (parents) {
+ struct commit *commit = parents->item;
+ parse_commit(commit);
+ if (!(commit->object.flags & mark)) {
+ commit->object.flags |= mark;
+ insert_by_date(commit, list);
+ }
+ parents = parents->next;
+ }
+ return ret;
+}
+
+/*
+ * Generic support for pretty-printing the header
+ */
+static int get_one_line(const char *msg, unsigned long len)
+{
+ int ret = 0;
+
+ while (len--) {
+ char c = *msg++;
+ ret++;
+ if (c == '\n')
+ break;
+ if (!c)
+ return 0;
+ }
+ return ret;
+}
+
+static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line)
+{
+ char *date;
+ int namelen;
+ unsigned long time;
+ int tz, ret;
+ const char *filler = " ";
+
+ if (fmt == CMIT_FMT_ONELINE)
+ return 0;
+ date = strchr(line, '>');
+ if (!date)
+ return 0;
+ namelen = ++date - line;
+ time = strtoul(date, &date, 10);
+ tz = strtol(date, NULL, 10);
+
+ ret = sprintf(buf, "%s: %.*s%.*s\n", what,
+ (fmt == CMIT_FMT_FULLER) ? 4 : 0,
+ filler, namelen, line);
+ switch (fmt) {
+ case CMIT_FMT_MEDIUM:
+ ret += sprintf(buf + ret, "Date: %s\n", show_date(time, tz));
+ break;
+ case CMIT_FMT_FULLER:
+ ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz));
+ break;
+ default:
+ /* notin' */
+ break;
+ }
+ return ret;
+}
+
+static int is_empty_line(const char *line, int len)
+{
+ while (len && isspace(line[len-1]))
+ len--;
+ return !len;
+}
+
+static int add_parent_info(enum cmit_fmt fmt, char *buf, const char *line, int parents)
+{
+ int offset = 0;
+
+ if (fmt == CMIT_FMT_ONELINE)
+ return offset;
+ switch (parents) {
+ case 1:
+ break;
+ case 2:
+ /* Go back to the previous line: 40 characters of previous parent, and one '\n' */
+ offset = sprintf(buf, "Merge: %.40s\n", line-41);
+ /* Fallthrough */
+ default:
+ /* Replace the previous '\n' with a space */
+ buf[offset-1] = ' ';
+ offset += sprintf(buf + offset, "%.40s\n", line+7);
+ }
+ return offset;
+}
+
+unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space)
+{
+ int hdr = 1, body = 0;
+ unsigned long offset = 0;
+ int parents = 0;
+ int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4;
+
+ for (;;) {
+ const char *line = msg;
+ int linelen = get_one_line(msg, len);
+
+ if (!linelen)
+ break;
+
+ /*
+ * We want some slop for indentation and a possible
+ * final "...". Thus the "+ 20".
+ */
+ if (offset + linelen + 20 > space) {
+ memcpy(buf + offset, " ...\n", 8);
+ offset += 8;
+ break;
+ }
+
+ msg += linelen;
+ len -= linelen;
+ if (hdr) {
+ if (linelen == 1) {
+ hdr = 0;
+ if (fmt != CMIT_FMT_ONELINE)
+ buf[offset++] = '\n';
+ continue;
+ }
+ if (fmt == CMIT_FMT_RAW) {
+ memcpy(buf + offset, line, linelen);
+ offset += linelen;
+ continue;
+ }
+ if (!memcmp(line, "parent ", 7)) {
+ if (linelen != 48)
+ die("bad parent line in commit");
+ offset += add_parent_info(fmt, buf + offset, line, ++parents);
+ }
+
+ /*
+ * MEDIUM == DEFAULT shows only author with dates.
+ * FULL shows both authors but not dates.
+ * FULLER shows both authors and dates.
+ */
+ if (!memcmp(line, "author ", 7))
+ offset += add_user_info("Author", fmt,
+ buf + offset,
+ line + 7);
+ if (!memcmp(line, "committer ", 10) &&
+ (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
+ offset += add_user_info("Commit", fmt,
+ buf + offset,
+ line + 10);
+ continue;
+ }
+
+ if (is_empty_line(line, linelen)) {
+ if (!body)
+ continue;
+ if (fmt == CMIT_FMT_SHORT)
+ break;
+ } else {
+ body = 1;
+ }
+
+ memset(buf + offset, ' ', indent);
+ memcpy(buf + offset + indent, line, linelen);
+ offset += linelen + indent;
+ if (fmt == CMIT_FMT_ONELINE)
+ break;
+ }
+ if (fmt == CMIT_FMT_ONELINE) {
+ /* We do not want the terminating newline */
+ if (buf[offset - 1] == '\n')
+ offset--;
+ }
+ else {
+ /* Make sure there is an EOLN */
+ if (buf[offset - 1] != '\n')
+ buf[offset++] = '\n';
+ }
+ buf[offset] = '\0';
+ return offset;
+}
+
+struct commit *pop_commit(struct commit_list **stack)
+{
+ struct commit_list *top = *stack;
+ struct commit *item = top ? top->item : NULL;
+
+ if (top) {
+ *stack = top->next;
+ free(top);
+ }
+ return item;
+}
+
+int count_parents(struct commit * commit)
+{
+ int count = 0;
+ struct commit_list * parents = commit->parents;
+ for (count=0;parents; parents=parents->next,count++)
+ ;
+ return count;
+}
+
+/*
+ * Performs an in-place topological sort on the list supplied.
+ */
+void sort_in_topological_order(struct commit_list ** list)
+{
+ struct commit_list * next = *list;
+ struct commit_list * work = NULL, **insert;
+ struct commit_list ** pptr = list;
+ struct sort_node * nodes;
+ struct sort_node * next_nodes;
+ int count = 0;
+
+ /* determine the size of the list */
+ while (next) {
+ next = next->next;
+ count++;
+ }
+ /* allocate an array to help sort the list */
+ nodes = xcalloc(count, sizeof(*nodes));
+ /* link the list to the array */
+ next_nodes = nodes;
+ next=*list;
+ while (next) {
+ next_nodes->list_item = next;
+ next->item->object.util = next_nodes;
+ next_nodes++;
+ next = next->next;
+ }
+ /* update the indegree */
+ next=*list;
+ while (next) {
+ struct commit_list * parents = next->item->parents;
+ while (parents) {
+ struct commit * parent=parents->item;
+ struct sort_node * pn = (struct sort_node *)parent->object.util;
+
+ if (pn)
+ pn->indegree++;
+ parents=parents->next;
+ }
+ next=next->next;
+ }
+ /*
+ * find the tips
+ *
+ * tips are nodes not reachable from any other node in the list
+ *
+ * the tips serve as a starting set for the work queue.
+ */
+ next=*list;
+ insert = &work;
+ while (next) {
+ struct sort_node * node = (struct sort_node *)next->item->object.util;
+
+ if (node->indegree == 0) {
+ insert = &commit_list_insert(next->item, insert)->next;
+ }
+ next=next->next;
+ }
+ /* process the list in topological order */
+ while (work) {
+ struct commit * work_item = pop_commit(&work);
+ struct sort_node * work_node = (struct sort_node *)work_item->object.util;
+ struct commit_list * parents = work_item->parents;
+
+ while (parents) {
+ struct commit * parent=parents->item;
+ struct sort_node * pn = (struct sort_node *)parent->object.util;
+
+ if (pn) {
+ /*
+ * parents are only enqueued for emission
+ * when all their children have been emitted thereby
+ * guaranteeing topological order.
+ */
+ pn->indegree--;
+ if (!pn->indegree)
+ commit_list_insert(parent, &work);
+ }
+ parents=parents->next;
+ }
+ /*
+ * work_item is a commit all of whose children
+ * have already been emitted. we can emit it now.
+ */
+ *pptr = work_node->list_item;
+ pptr = &(*pptr)->next;
+ *pptr = NULL;
+ work_item->object.util = NULL;
+ }
+ free(nodes);
+}
diff --git a/commit.h b/commit.h
new file mode 100644
index 000000000..6738a696d
--- /dev/null
+++ b/commit.h
@@ -0,0 +1,75 @@
+#ifndef COMMIT_H
+#define COMMIT_H
+
+#include "object.h"
+#include "tree.h"
+
+struct commit_list {
+ struct commit *item;
+ struct commit_list *next;
+};
+
+struct commit {
+ struct object object;
+ unsigned long date;
+ struct commit_list *parents;
+ struct tree *tree;
+ char *buffer;
+};
+
+extern int save_commit_buffer;
+extern const char *commit_type;
+
+struct commit *lookup_commit(const unsigned char *sha1);
+struct commit *lookup_commit_reference(const unsigned char *sha1);
+struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
+ int quiet);
+
+int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size);
+
+int parse_commit(struct commit *item);
+
+struct commit_list * commit_list_insert(struct commit *item, struct commit_list **list_p);
+struct commit_list * insert_by_date(struct commit *item, struct commit_list **list);
+
+void free_commit_list(struct commit_list *list);
+
+void sort_by_date(struct commit_list **list);
+
+/* Commit formats */
+enum cmit_fmt {
+ CMIT_FMT_RAW,
+ CMIT_FMT_MEDIUM,
+ CMIT_FMT_DEFAULT = CMIT_FMT_MEDIUM,
+ CMIT_FMT_SHORT,
+ CMIT_FMT_FULL,
+ CMIT_FMT_FULLER,
+ CMIT_FMT_ONELINE,
+};
+
+extern enum cmit_fmt get_commit_format(const char *arg);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const char *msg, unsigned long len, char *buf, unsigned long space);
+
+/** Removes the first commit from a list sorted by date, and adds all
+ * of its parents.
+ **/
+struct commit *pop_most_recent_commit(struct commit_list **list,
+ unsigned int mark);
+
+struct commit *pop_commit(struct commit_list **stack);
+
+int count_parents(struct commit * commit);
+
+/*
+ * Performs an in-place topological sort of list supplied.
+ *
+ * Pre-conditions:
+ * all commits in input list and all parents of those
+ * commits must have object.util == NULL
+ *
+ * Post-conditions:
+ * invariant of resulting list is:
+ * a reachable from b => ord(b) < ord(a)
+ */
+void sort_in_topological_order(struct commit_list ** list);
+#endif /* COMMIT_H */
diff --git a/compat/mmap.c b/compat/mmap.c
new file mode 100644
index 000000000..a051c4767
--- /dev/null
+++ b/compat/mmap.c
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include "../cache.h"
+
+void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
+{
+ int n = 0;
+
+ if (start != NULL || !(flags & MAP_PRIVATE))
+ die("Invalid usage of gitfakemmap.");
+
+ if (lseek(fd, offset, SEEK_SET) < 0) {
+ errno = EINVAL;
+ return MAP_FAILED;
+ }
+
+ start = xmalloc(length);
+ if (start == NULL) {
+ errno = ENOMEM;
+ return MAP_FAILED;
+ }
+
+ while (n < length) {
+ int count = read(fd, start+n, length-n);
+
+ if (count == 0) {
+ memset(start+n, 0, length-n);
+ break;
+ }
+
+ if (count < 0) {
+ free(start);
+ errno = EACCES;
+ return MAP_FAILED;
+ }
+
+ n += count;
+ }
+
+ return start;
+}
+
+int gitfakemunmap(void *start, size_t length)
+{
+ free(start);
+ return 0;
+}
+
diff --git a/compat/strcasestr.c b/compat/strcasestr.c
new file mode 100644
index 000000000..b96414d36
--- /dev/null
+++ b/compat/strcasestr.c
@@ -0,0 +1,23 @@
+#include <string.h>
+#include <ctype.h>
+
+char *gitstrcasestr(const char *haystack, const char *needle)
+{
+ int nlen = strlen(needle);
+ int hlen = strlen(haystack) - nlen + 1;
+ int i;
+
+ for (i = 0; i < hlen; i++) {
+ int j;
+ for (j = 0; j < nlen; j++) {
+ unsigned char c1 = haystack[i+j];
+ unsigned char c2 = needle[j];
+ if (toupper(c1) != toupper(c2))
+ goto next;
+ }
+ return (char *) haystack + i;
+ next:
+ ;
+ }
+ return NULL;
+}
diff --git a/compat/subprocess.py b/compat/subprocess.py
new file mode 100644
index 000000000..bbd26c7b0
--- /dev/null
+++ b/compat/subprocess.py
@@ -0,0 +1,1149 @@
+# subprocess - Subprocesses with accessible I/O streams
+#
+# For more information about this module, see PEP 324.
+#
+# This module should remain compatible with Python 2.2, see PEP 291.
+#
+# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se>
+#
+# Licensed to PSF under a Contributor Agreement.
+# See http://www.python.org/2.4/license for licensing details.
+
+r"""subprocess - Subprocesses with accessible I/O streams
+
+This module allows you to spawn processes, connect to their
+input/output/error pipes, and obtain their return codes. This module
+intends to replace several other, older modules and functions, like:
+
+os.system
+os.spawn*
+os.popen*
+popen2.*
+commands.*
+
+Information about how the subprocess module can be used to replace these
+modules and functions can be found below.
+
+
+
+Using the subprocess module
+===========================
+This module defines one class called Popen:
+
+class Popen(args, bufsize=0, executable=None,
+ stdin=None, stdout=None, stderr=None,
+ preexec_fn=None, close_fds=False, shell=False,
+ cwd=None, env=None, universal_newlines=False,
+ startupinfo=None, creationflags=0):
+
+
+Arguments are:
+
+args should be a string, or a sequence of program arguments. The
+program to execute is normally the first item in the args sequence or
+string, but can be explicitly set by using the executable argument.
+
+On UNIX, with shell=False (default): In this case, the Popen class
+uses os.execvp() to execute the child program. args should normally
+be a sequence. A string will be treated as a sequence with the string
+as the only item (the program to execute).
+
+On UNIX, with shell=True: If args is a string, it specifies the
+command string to execute through the shell. If args is a sequence,
+the first item specifies the command string, and any additional items
+will be treated as additional shell arguments.
+
+On Windows: the Popen class uses CreateProcess() to execute the child
+program, which operates on strings. If args is a sequence, it will be
+converted to a string using the list2cmdline method. Please note that
+not all MS Windows applications interpret the command line the same
+way: The list2cmdline is designed for applications using the same
+rules as the MS C runtime.
+
+bufsize, if given, has the same meaning as the corresponding argument
+to the built-in open() function: 0 means unbuffered, 1 means line
+buffered, any other positive value means use a buffer of
+(approximately) that size. A negative bufsize means to use the system
+default, which usually means fully buffered. The default value for
+bufsize is 0 (unbuffered).
+
+stdin, stdout and stderr specify the executed programs' standard
+input, standard output and standard error file handles, respectively.
+Valid values are PIPE, an existing file descriptor (a positive
+integer), an existing file object, and None. PIPE indicates that a
+new pipe to the child should be created. With None, no redirection
+will occur; the child's file handles will be inherited from the
+parent. Additionally, stderr can be STDOUT, which indicates that the
+stderr data from the applications should be captured into the same
+file handle as for stdout.
+
+If preexec_fn is set to a callable object, this object will be called
+in the child process just before the child is executed.
+
+If close_fds is true, all file descriptors except 0, 1 and 2 will be
+closed before the child process is executed.
+
+if shell is true, the specified command will be executed through the
+shell.
+
+If cwd is not None, the current directory will be changed to cwd
+before the child is executed.
+
+If env is not None, it defines the environment variables for the new
+process.
+
+If universal_newlines is true, the file objects stdout and stderr are
+opened as a text files, but lines may be terminated by any of '\n',
+the Unix end-of-line convention, '\r', the Macintosh convention or
+'\r\n', the Windows convention. All of these external representations
+are seen as '\n' by the Python program. Note: This feature is only
+available if Python is built with universal newline support (the
+default). Also, the newlines attribute of the file objects stdout,
+stdin and stderr are not updated by the communicate() method.
+
+The startupinfo and creationflags, if given, will be passed to the
+underlying CreateProcess() function. They can specify things such as
+appearance of the main window and priority for the new process.
+(Windows only)
+
+
+This module also defines two shortcut functions:
+
+call(*args, **kwargs):
+ Run command with arguments. Wait for command to complete, then
+ return the returncode attribute.
+
+ The arguments are the same as for the Popen constructor. Example:
+
+ retcode = call(["ls", "-l"])
+
+
+Exceptions
+----------
+Exceptions raised in the child process, before the new program has
+started to execute, will be re-raised in the parent. Additionally,
+the exception object will have one extra attribute called
+'child_traceback', which is a string containing traceback information
+from the childs point of view.
+
+The most common exception raised is OSError. This occurs, for
+example, when trying to execute a non-existent file. Applications
+should prepare for OSErrors.
+
+A ValueError will be raised if Popen is called with invalid arguments.
+
+
+Security
+--------
+Unlike some other popen functions, this implementation will never call
+/bin/sh implicitly. This means that all characters, including shell
+metacharacters, can safely be passed to child processes.
+
+
+Popen objects
+=============
+Instances of the Popen class have the following methods:
+
+poll()
+ Check if child process has terminated. Returns returncode
+ attribute.
+
+wait()
+ Wait for child process to terminate. Returns returncode attribute.
+
+communicate(input=None)
+ Interact with process: Send data to stdin. Read data from stdout
+ and stderr, until end-of-file is reached. Wait for process to
+ terminate. The optional stdin argument should be a string to be
+ sent to the child process, or None, if no data should be sent to
+ the child.
+
+ communicate() returns a tuple (stdout, stderr).
+
+ Note: The data read is buffered in memory, so do not use this
+ method if the data size is large or unlimited.
+
+The following attributes are also available:
+
+stdin
+ If the stdin argument is PIPE, this attribute is a file object
+ that provides input to the child process. Otherwise, it is None.
+
+stdout
+ If the stdout argument is PIPE, this attribute is a file object
+ that provides output from the child process. Otherwise, it is
+ None.
+
+stderr
+ If the stderr argument is PIPE, this attribute is file object that
+ provides error output from the child process. Otherwise, it is
+ None.
+
+pid
+ The process ID of the child process.
+
+returncode
+ The child return code. A None value indicates that the process
+ hasn't terminated yet. A negative value -N indicates that the
+ child was terminated by signal N (UNIX only).
+
+
+Replacing older functions with the subprocess module
+====================================================
+In this section, "a ==> b" means that b can be used as a replacement
+for a.
+
+Note: All functions in this section fail (more or less) silently if
+the executed program cannot be found; this module raises an OSError
+exception.
+
+In the following examples, we assume that the subprocess module is
+imported with "from subprocess import *".
+
+
+Replacing /bin/sh shell backquote
+---------------------------------
+output=`mycmd myarg`
+==>
+output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0]
+
+
+Replacing shell pipe line
+-------------------------
+output=`dmesg | grep hda`
+==>
+p1 = Popen(["dmesg"], stdout=PIPE)
+p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
+output = p2.communicate()[0]
+
+
+Replacing os.system()
+---------------------
+sts = os.system("mycmd" + " myarg")
+==>
+p = Popen("mycmd" + " myarg", shell=True)
+sts = os.waitpid(p.pid, 0)
+
+Note:
+
+* Calling the program through the shell is usually not required.
+
+* It's easier to look at the returncode attribute than the
+ exitstatus.
+
+A more real-world example would look like this:
+
+try:
+ retcode = call("mycmd" + " myarg", shell=True)
+ if retcode < 0:
+ print >>sys.stderr, "Child was terminated by signal", -retcode
+ else:
+ print >>sys.stderr, "Child returned", retcode
+except OSError, e:
+ print >>sys.stderr, "Execution failed:", e
+
+
+Replacing os.spawn*
+-------------------
+P_NOWAIT example:
+
+pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg")
+==>
+pid = Popen(["/bin/mycmd", "myarg"]).pid
+
+
+P_WAIT example:
+
+retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg")
+==>
+retcode = call(["/bin/mycmd", "myarg"])
+
+
+Vector example:
+
+os.spawnvp(os.P_NOWAIT, path, args)
+==>
+Popen([path] + args[1:])
+
+
+Environment example:
+
+os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env)
+==>
+Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"})
+
+
+Replacing os.popen*
+-------------------
+pipe = os.popen(cmd, mode='r', bufsize)
+==>
+pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout
+
+pipe = os.popen(cmd, mode='w', bufsize)
+==>
+pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
+
+
+(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize)
+==>
+p = Popen(cmd, shell=True, bufsize=bufsize,
+ stdin=PIPE, stdout=PIPE, close_fds=True)
+(child_stdin, child_stdout) = (p.stdin, p.stdout)
+
+
+(child_stdin,
+ child_stdout,
+ child_stderr) = os.popen3(cmd, mode, bufsize)
+==>
+p = Popen(cmd, shell=True, bufsize=bufsize,
+ stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
+(child_stdin,
+ child_stdout,
+ child_stderr) = (p.stdin, p.stdout, p.stderr)
+
+
+(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize)
+==>
+p = Popen(cmd, shell=True, bufsize=bufsize,
+ stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
+(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
+
+
+Replacing popen2.*
+------------------
+Note: If the cmd argument to popen2 functions is a string, the command
+is executed through /bin/sh. If it is a list, the command is directly
+executed.
+
+(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode)
+==>
+p = Popen(["somestring"], shell=True, bufsize=bufsize
+ stdin=PIPE, stdout=PIPE, close_fds=True)
+(child_stdout, child_stdin) = (p.stdout, p.stdin)
+
+
+(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode)
+==>
+p = Popen(["mycmd", "myarg"], bufsize=bufsize,
+ stdin=PIPE, stdout=PIPE, close_fds=True)
+(child_stdout, child_stdin) = (p.stdout, p.stdin)
+
+The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen,
+except that:
+
+* subprocess.Popen raises an exception if the execution fails
+* the capturestderr argument is replaced with the stderr argument.
+* stdin=PIPE and stdout=PIPE must be specified.
+* popen2 closes all filedescriptors by default, but you have to specify
+ close_fds=True with subprocess.Popen.
+
+
+"""
+
+import sys
+mswindows = (sys.platform == "win32")
+
+import os
+import types
+import traceback
+
+if mswindows:
+ import threading
+ import msvcrt
+ if 0: # <-- change this to use pywin32 instead of the _subprocess driver
+ import pywintypes
+ from win32api import GetStdHandle, STD_INPUT_HANDLE, \
+ STD_OUTPUT_HANDLE, STD_ERROR_HANDLE
+ from win32api import GetCurrentProcess, DuplicateHandle, \
+ GetModuleFileName, GetVersion
+ from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE
+ from win32pipe import CreatePipe
+ from win32process import CreateProcess, STARTUPINFO, \
+ GetExitCodeProcess, STARTF_USESTDHANDLES, \
+ STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
+ from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0
+ else:
+ from _subprocess import *
+ class STARTUPINFO:
+ dwFlags = 0
+ hStdInput = None
+ hStdOutput = None
+ hStdError = None
+ class pywintypes:
+ error = IOError
+else:
+ import select
+ import errno
+ import fcntl
+ import pickle
+
+__all__ = ["Popen", "PIPE", "STDOUT", "call"]
+
+try:
+ MAXFD = os.sysconf("SC_OPEN_MAX")
+except:
+ MAXFD = 256
+
+# True/False does not exist on 2.2.0
+try:
+ False
+except NameError:
+ False = 0
+ True = 1
+
+_active = []
+
+def _cleanup():
+ for inst in _active[:]:
+ inst.poll()
+
+PIPE = -1
+STDOUT = -2
+
+
+def call(*args, **kwargs):
+ """Run command with arguments. Wait for command to complete, then
+ return the returncode attribute.
+
+ The arguments are the same as for the Popen constructor. Example:
+
+ retcode = call(["ls", "-l"])
+ """
+ return Popen(*args, **kwargs).wait()
+
+
+def list2cmdline(seq):
+ """
+ Translate a sequence of arguments into a command line
+ string, using the same rules as the MS C runtime:
+
+ 1) Arguments are delimited by white space, which is either a
+ space or a tab.
+
+ 2) A string surrounded by double quotation marks is
+ interpreted as a single argument, regardless of white space
+ contained within. A quoted string can be embedded in an
+ argument.
+
+ 3) A double quotation mark preceded by a backslash is
+ interpreted as a literal double quotation mark.
+
+ 4) Backslashes are interpreted literally, unless they
+ immediately precede a double quotation mark.
+
+ 5) If backslashes immediately precede a double quotation mark,
+ every pair of backslashes is interpreted as a literal
+ backslash. If the number of backslashes is odd, the last
+ backslash escapes the next double quotation mark as
+ described in rule 3.
+ """
+
+ # See
+ # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp
+ result = []
+ needquote = False
+ for arg in seq:
+ bs_buf = []
+
+ # Add a space to separate this argument from the others
+ if result:
+ result.append(' ')
+
+ needquote = (" " in arg) or ("\t" in arg)
+ if needquote:
+ result.append('"')
+
+ for c in arg:
+ if c == '\\':
+ # Don't know if we need to double yet.
+ bs_buf.append(c)
+ elif c == '"':
+ # Double backspaces.
+ result.append('\\' * len(bs_buf)*2)
+ bs_buf = []
+ result.append('\\"')
+ else:
+ # Normal char
+ if bs_buf:
+ result.extend(bs_buf)
+ bs_buf = []
+ result.append(c)
+
+ # Add remaining backspaces, if any.
+ if bs_buf:
+ result.extend(bs_buf)
+
+ if needquote:
+ result.extend(bs_buf)
+ result.append('"')
+
+ return ''.join(result)
+
+
+class Popen(object):
+ def __init__(self, args, bufsize=0, executable=None,
+ stdin=None, stdout=None, stderr=None,
+ preexec_fn=None, close_fds=False, shell=False,
+ cwd=None, env=None, universal_newlines=False,
+ startupinfo=None, creationflags=0):
+ """Create new Popen instance."""
+ _cleanup()
+
+ if not isinstance(bufsize, (int, long)):
+ raise TypeError("bufsize must be an integer")
+
+ if mswindows:
+ if preexec_fn is not None:
+ raise ValueError("preexec_fn is not supported on Windows "
+ "platforms")
+ if close_fds:
+ raise ValueError("close_fds is not supported on Windows "
+ "platforms")
+ else:
+ # POSIX
+ if startupinfo is not None:
+ raise ValueError("startupinfo is only supported on Windows "
+ "platforms")
+ if creationflags != 0:
+ raise ValueError("creationflags is only supported on Windows "
+ "platforms")
+
+ self.stdin = None
+ self.stdout = None
+ self.stderr = None
+ self.pid = None
+ self.returncode = None
+ self.universal_newlines = universal_newlines
+
+ # Input and output objects. The general principle is like
+ # this:
+ #
+ # Parent Child
+ # ------ -----
+ # p2cwrite ---stdin---> p2cread
+ # c2pread <--stdout--- c2pwrite
+ # errread <--stderr--- errwrite
+ #
+ # On POSIX, the child objects are file descriptors. On
+ # Windows, these are Windows file handles. The parent objects
+ # are file descriptors on both platforms. The parent objects
+ # are None when not using PIPEs. The child objects are None
+ # when not redirecting.
+
+ (p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite) = self._get_handles(stdin, stdout, stderr)
+
+ self._execute_child(args, executable, preexec_fn, close_fds,
+ cwd, env, universal_newlines,
+ startupinfo, creationflags, shell,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite)
+
+ if p2cwrite:
+ self.stdin = os.fdopen(p2cwrite, 'wb', bufsize)
+ if c2pread:
+ if universal_newlines:
+ self.stdout = os.fdopen(c2pread, 'rU', bufsize)
+ else:
+ self.stdout = os.fdopen(c2pread, 'rb', bufsize)
+ if errread:
+ if universal_newlines:
+ self.stderr = os.fdopen(errread, 'rU', bufsize)
+ else:
+ self.stderr = os.fdopen(errread, 'rb', bufsize)
+
+ _active.append(self)
+
+
+ def _translate_newlines(self, data):
+ data = data.replace("\r\n", "\n")
+ data = data.replace("\r", "\n")
+ return data
+
+
+ if mswindows:
+ #
+ # Windows methods
+ #
+ def _get_handles(self, stdin, stdout, stderr):
+ """Construct and return tupel with IO objects:
+ p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
+ """
+ if stdin == None and stdout == None and stderr == None:
+ return (None, None, None, None, None, None)
+
+ p2cread, p2cwrite = None, None
+ c2pread, c2pwrite = None, None
+ errread, errwrite = None, None
+
+ if stdin == None:
+ p2cread = GetStdHandle(STD_INPUT_HANDLE)
+ elif stdin == PIPE:
+ p2cread, p2cwrite = CreatePipe(None, 0)
+ # Detach and turn into fd
+ p2cwrite = p2cwrite.Detach()
+ p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0)
+ elif type(stdin) == types.IntType:
+ p2cread = msvcrt.get_osfhandle(stdin)
+ else:
+ # Assuming file-like object
+ p2cread = msvcrt.get_osfhandle(stdin.fileno())
+ p2cread = self._make_inheritable(p2cread)
+
+ if stdout == None:
+ c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE)
+ elif stdout == PIPE:
+ c2pread, c2pwrite = CreatePipe(None, 0)
+ # Detach and turn into fd
+ c2pread = c2pread.Detach()
+ c2pread = msvcrt.open_osfhandle(c2pread, 0)
+ elif type(stdout) == types.IntType:
+ c2pwrite = msvcrt.get_osfhandle(stdout)
+ else:
+ # Assuming file-like object
+ c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
+ c2pwrite = self._make_inheritable(c2pwrite)
+
+ if stderr == None:
+ errwrite = GetStdHandle(STD_ERROR_HANDLE)
+ elif stderr == PIPE:
+ errread, errwrite = CreatePipe(None, 0)
+ # Detach and turn into fd
+ errread = errread.Detach()
+ errread = msvcrt.open_osfhandle(errread, 0)
+ elif stderr == STDOUT:
+ errwrite = c2pwrite
+ elif type(stderr) == types.IntType:
+ errwrite = msvcrt.get_osfhandle(stderr)
+ else:
+ # Assuming file-like object
+ errwrite = msvcrt.get_osfhandle(stderr.fileno())
+ errwrite = self._make_inheritable(errwrite)
+
+ return (p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite)
+
+
+ def _make_inheritable(self, handle):
+ """Return a duplicate of handle, which is inheritable"""
+ return DuplicateHandle(GetCurrentProcess(), handle,
+ GetCurrentProcess(), 0, 1,
+ DUPLICATE_SAME_ACCESS)
+
+
+ def _find_w9xpopen(self):
+ """Find and return absolut path to w9xpopen.exe"""
+ w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)),
+ "w9xpopen.exe")
+ if not os.path.exists(w9xpopen):
+ # Eeek - file-not-found - possibly an embedding
+ # situation - see if we can locate it in sys.exec_prefix
+ w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix),
+ "w9xpopen.exe")
+ if not os.path.exists(w9xpopen):
+ raise RuntimeError("Cannot locate w9xpopen.exe, which is "
+ "needed for Popen to work with your "
+ "shell or platform.")
+ return w9xpopen
+
+
+ def _execute_child(self, args, executable, preexec_fn, close_fds,
+ cwd, env, universal_newlines,
+ startupinfo, creationflags, shell,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite):
+ """Execute program (MS Windows version)"""
+
+ if not isinstance(args, types.StringTypes):
+ args = list2cmdline(args)
+
+ # Process startup details
+ default_startupinfo = STARTUPINFO()
+ if startupinfo == None:
+ startupinfo = default_startupinfo
+ if not None in (p2cread, c2pwrite, errwrite):
+ startupinfo.dwFlags |= STARTF_USESTDHANDLES
+ startupinfo.hStdInput = p2cread
+ startupinfo.hStdOutput = c2pwrite
+ startupinfo.hStdError = errwrite
+
+ if shell:
+ default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW
+ default_startupinfo.wShowWindow = SW_HIDE
+ comspec = os.environ.get("COMSPEC", "cmd.exe")
+ args = comspec + " /c " + args
+ if (GetVersion() >= 0x80000000L or
+ os.path.basename(comspec).lower() == "command.com"):
+ # Win9x, or using command.com on NT. We need to
+ # use the w9xpopen intermediate program. For more
+ # information, see KB Q150956
+ # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp)
+ w9xpopen = self._find_w9xpopen()
+ args = '"%s" %s' % (w9xpopen, args)
+ # Not passing CREATE_NEW_CONSOLE has been known to
+ # cause random failures on win9x. Specifically a
+ # dialog: "Your program accessed mem currently in
+ # use at xxx" and a hopeful warning about the
+ # stability of your system. Cost is Ctrl+C wont
+ # kill children.
+ creationflags |= CREATE_NEW_CONSOLE
+
+ # Start the process
+ try:
+ hp, ht, pid, tid = CreateProcess(executable, args,
+ # no special security
+ None, None,
+ # must inherit handles to pass std
+ # handles
+ 1,
+ creationflags,
+ env,
+ cwd,
+ startupinfo)
+ except pywintypes.error, e:
+ # Translate pywintypes.error to WindowsError, which is
+ # a subclass of OSError. FIXME: We should really
+ # translate errno using _sys_errlist (or simliar), but
+ # how can this be done from Python?
+ raise WindowsError(*e.args)
+
+ # Retain the process handle, but close the thread handle
+ self._handle = hp
+ self.pid = pid
+ ht.Close()
+
+ # Child is launched. Close the parent's copy of those pipe
+ # handles that only the child should have open. You need
+ # to make sure that no handles to the write end of the
+ # output pipe are maintained in this process or else the
+ # pipe will not close when the child process exits and the
+ # ReadFile will hang.
+ if p2cread != None:
+ p2cread.Close()
+ if c2pwrite != None:
+ c2pwrite.Close()
+ if errwrite != None:
+ errwrite.Close()
+
+
+ def poll(self):
+ """Check if child process has terminated. Returns returncode
+ attribute."""
+ if self.returncode == None:
+ if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0:
+ self.returncode = GetExitCodeProcess(self._handle)
+ _active.remove(self)
+ return self.returncode
+
+
+ def wait(self):
+ """Wait for child process to terminate. Returns returncode
+ attribute."""
+ if self.returncode == None:
+ obj = WaitForSingleObject(self._handle, INFINITE)
+ self.returncode = GetExitCodeProcess(self._handle)
+ _active.remove(self)
+ return self.returncode
+
+
+ def _readerthread(self, fh, buffer):
+ buffer.append(fh.read())
+
+
+ def communicate(self, input=None):
+ """Interact with process: Send data to stdin. Read data from
+ stdout and stderr, until end-of-file is reached. Wait for
+ process to terminate. The optional input argument should be a
+ string to be sent to the child process, or None, if no data
+ should be sent to the child.
+
+ communicate() returns a tuple (stdout, stderr)."""
+ stdout = None # Return
+ stderr = None # Return
+
+ if self.stdout:
+ stdout = []
+ stdout_thread = threading.Thread(target=self._readerthread,
+ args=(self.stdout, stdout))
+ stdout_thread.setDaemon(True)
+ stdout_thread.start()
+ if self.stderr:
+ stderr = []
+ stderr_thread = threading.Thread(target=self._readerthread,
+ args=(self.stderr, stderr))
+ stderr_thread.setDaemon(True)
+ stderr_thread.start()
+
+ if self.stdin:
+ if input != None:
+ self.stdin.write(input)
+ self.stdin.close()
+
+ if self.stdout:
+ stdout_thread.join()
+ if self.stderr:
+ stderr_thread.join()
+
+ # All data exchanged. Translate lists into strings.
+ if stdout != None:
+ stdout = stdout[0]
+ if stderr != None:
+ stderr = stderr[0]
+
+ # Translate newlines, if requested. We cannot let the file
+ # object do the translation: It is based on stdio, which is
+ # impossible to combine with select (unless forcing no
+ # buffering).
+ if self.universal_newlines and hasattr(open, 'newlines'):
+ if stdout:
+ stdout = self._translate_newlines(stdout)
+ if stderr:
+ stderr = self._translate_newlines(stderr)
+
+ self.wait()
+ return (stdout, stderr)
+
+ else:
+ #
+ # POSIX methods
+ #
+ def _get_handles(self, stdin, stdout, stderr):
+ """Construct and return tupel with IO objects:
+ p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
+ """
+ p2cread, p2cwrite = None, None
+ c2pread, c2pwrite = None, None
+ errread, errwrite = None, None
+
+ if stdin == None:
+ pass
+ elif stdin == PIPE:
+ p2cread, p2cwrite = os.pipe()
+ elif type(stdin) == types.IntType:
+ p2cread = stdin
+ else:
+ # Assuming file-like object
+ p2cread = stdin.fileno()
+
+ if stdout == None:
+ pass
+ elif stdout == PIPE:
+ c2pread, c2pwrite = os.pipe()
+ elif type(stdout) == types.IntType:
+ c2pwrite = stdout
+ else:
+ # Assuming file-like object
+ c2pwrite = stdout.fileno()
+
+ if stderr == None:
+ pass
+ elif stderr == PIPE:
+ errread, errwrite = os.pipe()
+ elif stderr == STDOUT:
+ errwrite = c2pwrite
+ elif type(stderr) == types.IntType:
+ errwrite = stderr
+ else:
+ # Assuming file-like object
+ errwrite = stderr.fileno()
+
+ return (p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite)
+
+
+ def _set_cloexec_flag(self, fd):
+ try:
+ cloexec_flag = fcntl.FD_CLOEXEC
+ except AttributeError:
+ cloexec_flag = 1
+
+ old = fcntl.fcntl(fd, fcntl.F_GETFD)
+ fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag)
+
+
+ def _close_fds(self, but):
+ for i in range(3, MAXFD):
+ if i == but:
+ continue
+ try:
+ os.close(i)
+ except:
+ pass
+
+
+ def _execute_child(self, args, executable, preexec_fn, close_fds,
+ cwd, env, universal_newlines,
+ startupinfo, creationflags, shell,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite):
+ """Execute program (POSIX version)"""
+
+ if isinstance(args, types.StringTypes):
+ args = [args]
+
+ if shell:
+ args = ["/bin/sh", "-c"] + args
+
+ if executable == None:
+ executable = args[0]
+
+ # For transferring possible exec failure from child to parent
+ # The first char specifies the exception type: 0 means
+ # OSError, 1 means some other error.
+ errpipe_read, errpipe_write = os.pipe()
+ self._set_cloexec_flag(errpipe_write)
+
+ self.pid = os.fork()
+ if self.pid == 0:
+ # Child
+ try:
+ # Close parent's pipe ends
+ if p2cwrite:
+ os.close(p2cwrite)
+ if c2pread:
+ os.close(c2pread)
+ if errread:
+ os.close(errread)
+ os.close(errpipe_read)
+
+ # Dup fds for child
+ if p2cread:
+ os.dup2(p2cread, 0)
+ if c2pwrite:
+ os.dup2(c2pwrite, 1)
+ if errwrite:
+ os.dup2(errwrite, 2)
+
+ # Close pipe fds. Make sure we doesn't close the same
+ # fd more than once.
+ if p2cread:
+ os.close(p2cread)
+ if c2pwrite and c2pwrite not in (p2cread,):
+ os.close(c2pwrite)
+ if errwrite and errwrite not in (p2cread, c2pwrite):
+ os.close(errwrite)
+
+ # Close all other fds, if asked for
+ if close_fds:
+ self._close_fds(but=errpipe_write)
+
+ if cwd != None:
+ os.chdir(cwd)
+
+ if preexec_fn:
+ apply(preexec_fn)
+
+ if env == None:
+ os.execvp(executable, args)
+ else:
+ os.execvpe(executable, args, env)
+
+ except:
+ exc_type, exc_value, tb = sys.exc_info()
+ # Save the traceback and attach it to the exception object
+ exc_lines = traceback.format_exception(exc_type,
+ exc_value,
+ tb)
+ exc_value.child_traceback = ''.join(exc_lines)
+ os.write(errpipe_write, pickle.dumps(exc_value))
+
+ # This exitcode won't be reported to applications, so it
+ # really doesn't matter what we return.
+ os._exit(255)
+
+ # Parent
+ os.close(errpipe_write)
+ if p2cread and p2cwrite:
+ os.close(p2cread)
+ if c2pwrite and c2pread:
+ os.close(c2pwrite)
+ if errwrite and errread:
+ os.close(errwrite)
+
+ # Wait for exec to fail or succeed; possibly raising exception
+ data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB
+ os.close(errpipe_read)
+ if data != "":
+ os.waitpid(self.pid, 0)
+ child_exception = pickle.loads(data)
+ raise child_exception
+
+
+ def _handle_exitstatus(self, sts):
+ if os.WIFSIGNALED(sts):
+ self.returncode = -os.WTERMSIG(sts)
+ elif os.WIFEXITED(sts):
+ self.returncode = os.WEXITSTATUS(sts)
+ else:
+ # Should never happen
+ raise RuntimeError("Unknown child exit status!")
+
+ _active.remove(self)
+
+
+ def poll(self):
+ """Check if child process has terminated. Returns returncode
+ attribute."""
+ if self.returncode == None:
+ try:
+ pid, sts = os.waitpid(self.pid, os.WNOHANG)
+ if pid == self.pid:
+ self._handle_exitstatus(sts)
+ except os.error:
+ pass
+ return self.returncode
+
+
+ def wait(self):
+ """Wait for child process to terminate. Returns returncode
+ attribute."""
+ if self.returncode == None:
+ pid, sts = os.waitpid(self.pid, 0)
+ self._handle_exitstatus(sts)
+ return self.returncode
+
+
+ def communicate(self, input=None):
+ """Interact with process: Send data to stdin. Read data from
+ stdout and stderr, until end-of-file is reached. Wait for
+ process to terminate. The optional input argument should be a
+ string to be sent to the child process, or None, if no data
+ should be sent to the child.
+
+ communicate() returns a tuple (stdout, stderr)."""
+ read_set = []
+ write_set = []
+ stdout = None # Return
+ stderr = None # Return
+
+ if self.stdin:
+ # Flush stdio buffer. This might block, if the user has
+ # been writing to .stdin in an uncontrolled fashion.
+ self.stdin.flush()
+ if input:
+ write_set.append(self.stdin)
+ else:
+ self.stdin.close()
+ if self.stdout:
+ read_set.append(self.stdout)
+ stdout = []
+ if self.stderr:
+ read_set.append(self.stderr)
+ stderr = []
+
+ while read_set or write_set:
+ rlist, wlist, xlist = select.select(read_set, write_set, [])
+
+ if self.stdin in wlist:
+ # When select has indicated that the file is writable,
+ # we can write up to PIPE_BUF bytes without risk
+ # blocking. POSIX defines PIPE_BUF >= 512
+ bytes_written = os.write(self.stdin.fileno(), input[:512])
+ input = input[bytes_written:]
+ if not input:
+ self.stdin.close()
+ write_set.remove(self.stdin)
+
+ if self.stdout in rlist:
+ data = os.read(self.stdout.fileno(), 1024)
+ if data == "":
+ self.stdout.close()
+ read_set.remove(self.stdout)
+ stdout.append(data)
+
+ if self.stderr in rlist:
+ data = os.read(self.stderr.fileno(), 1024)
+ if data == "":
+ self.stderr.close()
+ read_set.remove(self.stderr)
+ stderr.append(data)
+
+ # All data exchanged. Translate lists into strings.
+ if stdout != None:
+ stdout = ''.join(stdout)
+ if stderr != None:
+ stderr = ''.join(stderr)
+
+ # Translate newlines, if requested. We cannot let the file
+ # object do the translation: It is based on stdio, which is
+ # impossible to combine with select (unless forcing no
+ # buffering).
+ if self.universal_newlines and hasattr(open, 'newlines'):
+ if stdout:
+ stdout = self._translate_newlines(stdout)
+ if stderr:
+ stderr = self._translate_newlines(stderr)
+
+ self.wait()
+ return (stdout, stderr)
+
+
+def _demo_posix():
+ #
+ # Example 1: Simple redirection: Get process list
+ #
+ plist = Popen(["ps"], stdout=PIPE).communicate()[0]
+ print "Process list:"
+ print plist
+
+ #
+ # Example 2: Change uid before executing child
+ #
+ if os.getuid() == 0:
+ p = Popen(["id"], preexec_fn=lambda: os.setuid(100))
+ p.wait()
+
+ #
+ # Example 3: Connecting several subprocesses
+ #
+ print "Looking for 'hda'..."
+ p1 = Popen(["dmesg"], stdout=PIPE)
+ p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
+ print repr(p2.communicate()[0])
+
+ #
+ # Example 4: Catch execution error
+ #
+ print
+ print "Trying a weird file..."
+ try:
+ print Popen(["/this/path/does/not/exist"]).communicate()
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ print "The file didn't exist. I thought so..."
+ print "Child traceback:"
+ print e.child_traceback
+ else:
+ print "Error", e.errno
+ else:
+ print >>sys.stderr, "Gosh. No error."
+
+
+def _demo_windows():
+ #
+ # Example 1: Connecting several subprocesses
+ #
+ print "Looking for 'PROMPT' in set output..."
+ p1 = Popen("set", stdout=PIPE, shell=True)
+ p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE)
+ print repr(p2.communicate()[0])
+
+ #
+ # Example 2: Simple execution of program
+ #
+ print "Executing calc..."
+ p = Popen("calc")
+ p.wait()
+
+
+if __name__ == "__main__":
+ if mswindows:
+ _demo_windows()
+ else:
+ _demo_posix()
diff --git a/config.c b/config.c
new file mode 100644
index 000000000..915bb9752
--- /dev/null
+++ b/config.c
@@ -0,0 +1,254 @@
+
+#include "cache.h"
+
+#define MAXNAME (256)
+
+static FILE *config_file;
+static int config_linenr;
+static int get_next_char(void)
+{
+ int c;
+ FILE *f;
+
+ c = '\n';
+ if ((f = config_file) != NULL) {
+ c = fgetc(f);
+ if (c == '\r') {
+ /* DOS like systems */
+ c = fgetc(f);
+ if (c != '\n') {
+ ungetc(c, f);
+ c = '\r';
+ }
+ }
+ if (c == '\n')
+ config_linenr++;
+ if (c == EOF) {
+ config_file = NULL;
+ c = '\n';
+ }
+ }
+ return c;
+}
+
+static char *parse_value(void)
+{
+ static char value[1024];
+ int quote = 0, comment = 0, len = 0, space = 0;
+
+ for (;;) {
+ int c = get_next_char();
+ if (len >= sizeof(value))
+ return NULL;
+ if (c == '\n') {
+ if (quote)
+ return NULL;
+ value[len] = 0;
+ return value;
+ }
+ if (comment)
+ continue;
+ if (isspace(c) && !quote) {
+ space = 1;
+ continue;
+ }
+ if (space) {
+ if (len)
+ value[len++] = ' ';
+ space = 0;
+ }
+ if (c == '\\') {
+ c = get_next_char();
+ switch (c) {
+ case '\n':
+ continue;
+ case 't':
+ c = '\t';
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ /* Some characters escape as themselves */
+ case '\\': case '"':
+ break;
+ /* Reject unknown escape sequences */
+ default:
+ return NULL;
+ }
+ value[len++] = c;
+ continue;
+ }
+ if (c == '"') {
+ quote = 1-quote;
+ continue;
+ }
+ if (!quote) {
+ if (c == ';' || c == '#') {
+ comment = 1;
+ continue;
+ }
+ }
+ value[len++] = c;
+ }
+}
+
+static int get_value(config_fn_t fn, char *name, unsigned int len)
+{
+ int c;
+ char *value;
+
+ /* Get the full name */
+ for (;;) {
+ c = get_next_char();
+ if (c == EOF)
+ break;
+ if (!isalnum(c))
+ break;
+ name[len++] = tolower(c);
+ if (len >= MAXNAME)
+ return -1;
+ }
+ name[len] = 0;
+ while (c == ' ' || c == '\t')
+ c = get_next_char();
+
+ value = NULL;
+ if (c != '\n') {
+ if (c != '=')
+ return -1;
+ value = parse_value();
+ if (!value)
+ return -1;
+ }
+ return fn(name, value);
+}
+
+static int get_base_var(char *name)
+{
+ int baselen = 0;
+
+ for (;;) {
+ int c = get_next_char();
+ if (c == EOF)
+ return -1;
+ if (c == ']')
+ return baselen;
+ if (!isalnum(c))
+ return -1;
+ if (baselen > MAXNAME / 2)
+ return -1;
+ name[baselen++] = tolower(c);
+ }
+}
+
+static int git_parse_file(config_fn_t fn)
+{
+ int comment = 0;
+ int baselen = 0;
+ static char var[MAXNAME];
+
+ for (;;) {
+ int c = get_next_char();
+ if (c == '\n') {
+ /* EOF? */
+ if (!config_file)
+ return 0;
+ comment = 0;
+ continue;
+ }
+ if (comment || isspace(c))
+ continue;
+ if (c == '#' || c == ';') {
+ comment = 1;
+ continue;
+ }
+ if (c == '[') {
+ baselen = get_base_var(var);
+ if (baselen <= 0)
+ break;
+ var[baselen++] = '.';
+ var[baselen] = 0;
+ continue;
+ }
+ if (!isalpha(c))
+ break;
+ var[baselen] = tolower(c);
+ if (get_value(fn, var, baselen+1) < 0)
+ break;
+ }
+ die("bad config file line %d", config_linenr);
+}
+
+int git_config_int(const char *name, const char *value)
+{
+ if (value && *value) {
+ char *end;
+ int val = strtol(value, &end, 0);
+ if (!*end)
+ return val;
+ }
+ die("bad config value for '%s'", name);
+}
+
+int git_config_bool(const char *name, const char *value)
+{
+ if (!value)
+ return 1;
+ if (!*value)
+ return 0;
+ if (!strcasecmp(value, "true"))
+ return 1;
+ if (!strcasecmp(value, "false"))
+ return 0;
+ return git_config_int(name, value) != 0;
+}
+
+int git_default_config(const char *var, const char *value)
+{
+ /* This needs a better name */
+ if (!strcmp(var, "core.filemode")) {
+ trust_executable_bit = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (!strcmp(var, "core.symrefsonly")) {
+ only_use_symrefs = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (!strcmp(var, "user.name")) {
+ strncpy(git_default_name, value, sizeof(git_default_name));
+ return 0;
+ }
+
+ if (!strcmp(var, "user.email")) {
+ strncpy(git_default_email, value, sizeof(git_default_email));
+ return 0;
+ }
+
+ if (!strcmp(var, "diff.renamelimit")) {
+ diff_rename_limit_default = git_config_int(var, value);
+ return 0;
+ }
+
+ /* Add other config variables here.. */
+ return 0;
+}
+
+int git_config(config_fn_t fn)
+{
+ int ret;
+ FILE *f = fopen(git_path("config"), "r");
+
+ ret = -1;
+ if (f) {
+ config_file = f;
+ config_linenr = 1;
+ ret = git_parse_file(fn);
+ fclose(f);
+ }
+ return ret;
+}
diff --git a/connect.c b/connect.c
new file mode 100644
index 000000000..c2badc71a
--- /dev/null
+++ b/connect.c
@@ -0,0 +1,534 @@
+#include "cache.h"
+#include "pkt-line.h"
+#include "quote.h"
+#include "refs.h"
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+static char *server_capabilities = NULL;
+
+/*
+ * Read all the refs from the other end
+ */
+struct ref **get_remote_heads(int in, struct ref **list,
+ int nr_match, char **match, int ignore_funny)
+{
+ *list = NULL;
+ for (;;) {
+ struct ref *ref;
+ unsigned char old_sha1[20];
+ static char buffer[1000];
+ char *name;
+ int len, name_len;
+
+ len = packet_read_line(in, buffer, sizeof(buffer));
+ if (!len)
+ break;
+ if (buffer[len-1] == '\n')
+ buffer[--len] = 0;
+
+ if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
+ die("protocol error: expected sha/ref, got '%s'", buffer);
+ name = buffer + 41;
+
+ if (ignore_funny && 45 < len && !memcmp(name, "refs/", 5) &&
+ check_ref_format(name + 5))
+ continue;
+
+ name_len = strlen(name);
+ if (len != name_len + 41) {
+ if (server_capabilities)
+ free(server_capabilities);
+ server_capabilities = strdup(name + name_len + 1);
+ }
+
+ if (nr_match && !path_match(name, nr_match, match))
+ continue;
+ ref = xcalloc(1, sizeof(*ref) + len - 40);
+ memcpy(ref->old_sha1, old_sha1, 20);
+ memcpy(ref->name, buffer + 41, len - 40);
+ *list = ref;
+ list = &ref->next;
+ }
+ return list;
+}
+
+int server_supports(const char *feature)
+{
+ return server_capabilities &&
+ strstr(server_capabilities, feature) != NULL;
+}
+
+int get_ack(int fd, unsigned char *result_sha1)
+{
+ static char line[1000];
+ int len = packet_read_line(fd, line, sizeof(line));
+
+ if (!len)
+ die("git-fetch-pack: expected ACK/NAK, got EOF");
+ if (line[len-1] == '\n')
+ line[--len] = 0;
+ if (!strcmp(line, "NAK"))
+ return 0;
+ if (!strncmp(line, "ACK ", 3)) {
+ if (!get_sha1_hex(line+4, result_sha1)) {
+ if (strstr(line+45, "continue"))
+ return 2;
+ return 1;
+ }
+ }
+ die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+int path_match(const char *path, int nr, char **match)
+{
+ int i;
+ int pathlen = strlen(path);
+
+ for (i = 0; i < nr; i++) {
+ char *s = match[i];
+ int len = strlen(s);
+
+ if (!len || len > pathlen)
+ continue;
+ if (memcmp(path + pathlen - len, s, len))
+ continue;
+ if (pathlen > len && path[pathlen - len - 1] != '/')
+ continue;
+ *s = 0;
+ return 1;
+ }
+ return 0;
+}
+
+struct refspec {
+ char *src;
+ char *dst;
+ char force;
+};
+
+/*
+ * A:B means fast forward remote B with local A.
+ * +A:B means overwrite remote B with local A.
+ * +A is a shorthand for +A:A.
+ * A is a shorthand for A:A.
+ */
+static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
+{
+ int i;
+ struct refspec *rs = xcalloc(sizeof(*rs), (nr_refspec + 1));
+ for (i = 0; i < nr_refspec; i++) {
+ char *sp, *dp, *ep;
+ sp = refspec[i];
+ if (*sp == '+') {
+ rs[i].force = 1;
+ sp++;
+ }
+ ep = strchr(sp, ':');
+ if (ep) {
+ dp = ep + 1;
+ *ep = 0;
+ }
+ else
+ dp = sp;
+ rs[i].src = sp;
+ rs[i].dst = dp;
+ }
+ rs[nr_refspec].src = rs[nr_refspec].dst = NULL;
+ return rs;
+}
+
+static int count_refspec_match(const char *pattern,
+ struct ref *refs,
+ struct ref **matched_ref)
+{
+ int match;
+ int patlen = strlen(pattern);
+
+ for (match = 0; refs; refs = refs->next) {
+ char *name = refs->name;
+ int namelen = strlen(name);
+ if (namelen < patlen ||
+ memcmp(name + namelen - patlen, pattern, patlen))
+ continue;
+ if (namelen != patlen && name[namelen - patlen - 1] != '/')
+ continue;
+ match++;
+ *matched_ref = refs;
+ }
+ return match;
+}
+
+static void link_dst_tail(struct ref *ref, struct ref ***tail)
+{
+ **tail = ref;
+ *tail = &ref->next;
+ **tail = NULL;
+}
+
+static struct ref *try_explicit_object_name(const char *name)
+{
+ unsigned char sha1[20];
+ struct ref *ref;
+ int len;
+ if (get_sha1(name, sha1))
+ return NULL;
+ len = strlen(name) + 1;
+ ref = xcalloc(1, sizeof(*ref) + len);
+ memcpy(ref->name, name, len);
+ memcpy(ref->new_sha1, sha1, 20);
+ return ref;
+}
+
+static int match_explicit_refs(struct ref *src, struct ref *dst,
+ struct ref ***dst_tail, struct refspec *rs)
+{
+ int i, errs;
+ for (i = errs = 0; rs[i].src; i++) {
+ struct ref *matched_src, *matched_dst;
+
+ matched_src = matched_dst = NULL;
+ switch (count_refspec_match(rs[i].src, src, &matched_src)) {
+ case 1:
+ break;
+ case 0:
+ /* The source could be in the get_sha1() format
+ * not a reference name.
+ */
+ matched_src = try_explicit_object_name(rs[i].src);
+ if (matched_src)
+ break;
+ errs = 1;
+ error("src refspec %s does not match any.",
+ rs[i].src);
+ break;
+ default:
+ errs = 1;
+ error("src refspec %s matches more than one.",
+ rs[i].src);
+ break;
+ }
+ switch (count_refspec_match(rs[i].dst, dst, &matched_dst)) {
+ case 1:
+ break;
+ case 0:
+ if (!memcmp(rs[i].dst, "refs/", 5)) {
+ int len = strlen(rs[i].dst) + 1;
+ matched_dst = xcalloc(1, sizeof(*dst) + len);
+ memcpy(matched_dst->name, rs[i].dst, len);
+ link_dst_tail(matched_dst, dst_tail);
+ }
+ else if (!strcmp(rs[i].src, rs[i].dst) &&
+ matched_src) {
+ /* pushing "master:master" when
+ * remote does not have master yet.
+ */
+ int len = strlen(matched_src->name) + 1;
+ matched_dst = xcalloc(1, sizeof(*dst) + len);
+ memcpy(matched_dst->name, matched_src->name,
+ len);
+ link_dst_tail(matched_dst, dst_tail);
+ }
+ else {
+ errs = 1;
+ error("dst refspec %s does not match any "
+ "existing ref on the remote and does "
+ "not start with refs/.", rs[i].dst);
+ }
+ break;
+ default:
+ errs = 1;
+ error("dst refspec %s matches more than one.",
+ rs[i].dst);
+ break;
+ }
+ if (errs)
+ continue;
+ if (matched_dst->peer_ref) {
+ errs = 1;
+ error("dst ref %s receives from more than one src.",
+ matched_dst->name);
+ }
+ else {
+ matched_dst->peer_ref = matched_src;
+ matched_dst->force = rs[i].force;
+ }
+ }
+ return -errs;
+}
+
+static struct ref *find_ref_by_name(struct ref *list, const char *name)
+{
+ for ( ; list; list = list->next)
+ if (!strcmp(list->name, name))
+ return list;
+ return NULL;
+}
+
+int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+ int nr_refspec, char **refspec, int all)
+{
+ struct refspec *rs = parse_ref_spec(nr_refspec, refspec);
+
+ if (nr_refspec)
+ return match_explicit_refs(src, dst, dst_tail, rs);
+
+ /* pick the remainder */
+ for ( ; src; src = src->next) {
+ struct ref *dst_peer;
+ if (src->peer_ref)
+ continue;
+ dst_peer = find_ref_by_name(dst, src->name);
+ if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all))
+ continue;
+ if (!dst_peer) {
+ /* Create a new one and link it */
+ int len = strlen(src->name) + 1;
+ dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
+ memcpy(dst_peer->name, src->name, len);
+ memcpy(dst_peer->new_sha1, src->new_sha1, 20);
+ link_dst_tail(dst_peer, dst_tail);
+ }
+ dst_peer->peer_ref = src;
+ }
+ return 0;
+}
+
+enum protocol {
+ PROTO_LOCAL = 1,
+ PROTO_SSH,
+ PROTO_GIT,
+};
+
+static enum protocol get_protocol(const char *name)
+{
+ if (!strcmp(name, "ssh"))
+ return PROTO_SSH;
+ if (!strcmp(name, "git"))
+ return PROTO_GIT;
+ if (!strcmp(name, "git+ssh"))
+ return PROTO_SSH;
+ if (!strcmp(name, "ssh+git"))
+ return PROTO_SSH;
+ die("I don't handle protocol '%s'", name);
+}
+
+#define STR_(s) # s
+#define STR(s) STR_(s)
+
+#ifndef NO_IPV6
+
+static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
+{
+ int sockfd = -1;
+ char *colon, *end;
+ char *port = STR(DEFAULT_GIT_PORT);
+ struct addrinfo hints, *ai0, *ai;
+ int gai;
+
+ if (host[0] == '[') {
+ end = strchr(host + 1, ']');
+ if (end) {
+ *end = 0;
+ end++;
+ host++;
+ } else
+ end = host;
+ } else
+ end = host;
+ colon = strchr(end, ':');
+
+ if (colon) {
+ *colon = 0;
+ port = colon + 1;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ gai = getaddrinfo(host, port, &hints, &ai);
+ if (gai)
+ die("Unable to look up %s (%s)", host, gai_strerror(gai));
+
+ for (ai0 = ai; ai; ai = ai->ai_next) {
+ sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (sockfd < 0)
+ continue;
+ if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+ close(sockfd);
+ sockfd = -1;
+ continue;
+ }
+ break;
+ }
+
+ freeaddrinfo(ai0);
+
+ if (sockfd < 0)
+ die("unable to connect a socket (%s)", strerror(errno));
+
+ fd[0] = sockfd;
+ fd[1] = sockfd;
+ packet_write(sockfd, "%s %s\n", prog, path);
+ return 0;
+}
+
+#else /* NO_IPV6 */
+
+static int git_tcp_connect(int fd[2], const char *prog, char *host, char *path)
+{
+ int sockfd = -1;
+ char *colon, *end;
+ char *port = STR(DEFAULT_GIT_PORT), *ep;
+ struct hostent *he;
+ struct sockaddr_in sa;
+ char **ap;
+ unsigned int nport;
+
+ if (host[0] == '[') {
+ end = strchr(host + 1, ']');
+ if (end) {
+ *end = 0;
+ end++;
+ host++;
+ } else
+ end = host;
+ } else
+ end = host;
+ colon = strchr(end, ':');
+
+ if (colon) {
+ *colon = 0;
+ port = colon + 1;
+ }
+
+
+ he = gethostbyname(host);
+ if (!he)
+ die("Unable to look up %s (%s)", host, hstrerror(h_errno));
+ nport = strtoul(port, &ep, 10);
+ if ( ep == port || *ep ) {
+ /* Not numeric */
+ struct servent *se = getservbyname(port,"tcp");
+ if ( !se )
+ die("Unknown port %s\n", port);
+ nport = se->s_port;
+ }
+
+ for (ap = he->h_addr_list; *ap; ap++) {
+ sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
+ if (sockfd < 0)
+ continue;
+
+ memset(&sa, 0, sizeof sa);
+ sa.sin_family = he->h_addrtype;
+ sa.sin_port = htons(nport);
+ memcpy(&sa.sin_addr, ap, he->h_length);
+
+ if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
+ close(sockfd);
+ sockfd = -1;
+ continue;
+ }
+ break;
+ }
+
+ if (sockfd < 0)
+ die("unable to connect a socket (%s)", strerror(errno));
+
+ fd[0] = sockfd;
+ fd[1] = sockfd;
+ packet_write(sockfd, "%s %s\n", prog, path);
+ return 0;
+}
+
+#endif /* NO_IPV6 */
+
+/*
+ * Yeah, yeah, fixme. Need to pass in the heads etc.
+ */
+int git_connect(int fd[2], char *url, const char *prog)
+{
+ char command[1024];
+ char *host, *path;
+ char *colon;
+ int pipefd[2][2];
+ pid_t pid;
+ enum protocol protocol;
+
+ host = NULL;
+ path = url;
+ colon = strchr(url, ':');
+ protocol = PROTO_LOCAL;
+ if (colon) {
+ *colon = 0;
+ host = url;
+ path = colon+1;
+ protocol = PROTO_SSH;
+ if (!memcmp(path, "//", 2)) {
+ char *slash = strchr(path + 2, '/');
+ if (slash) {
+ int nr = slash - path - 2;
+ memmove(path, path+2, nr);
+ path[nr] = 0;
+ protocol = get_protocol(url);
+ host = path;
+ path = slash;
+ }
+ }
+ }
+
+ if (protocol == PROTO_GIT)
+ return git_tcp_connect(fd, prog, host, path);
+
+ if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
+ die("unable to create pipe pair for communication");
+ pid = fork();
+ if (!pid) {
+ snprintf(command, sizeof(command), "%s %s", prog,
+ sq_quote(path));
+ dup2(pipefd[1][0], 0);
+ dup2(pipefd[0][1], 1);
+ close(pipefd[0][0]);
+ close(pipefd[0][1]);
+ close(pipefd[1][0]);
+ close(pipefd[1][1]);
+ if (protocol == PROTO_SSH) {
+ const char *ssh, *ssh_basename;
+ ssh = getenv("GIT_SSH");
+ if (!ssh) ssh = "ssh";
+ ssh_basename = strrchr(ssh, '/');
+ if (!ssh_basename)
+ ssh_basename = ssh;
+ else
+ ssh_basename++;
+ execlp(ssh, ssh_basename, host, command, NULL);
+ }
+ else
+ execlp("sh", "sh", "-c", command, NULL);
+ die("exec failed");
+ }
+ fd[0] = pipefd[0][0];
+ fd[1] = pipefd[1][1];
+ close(pipefd[0][1]);
+ close(pipefd[1][0]);
+ return pid;
+}
+
+int finish_connect(pid_t pid)
+{
+ int ret;
+
+ for (;;) {
+ ret = waitpid(pid, NULL, 0);
+ if (!ret)
+ break;
+ if (errno != EINTR)
+ break;
+ }
+ return ret;
+}
diff --git a/convert-objects.c b/convert-objects.c
new file mode 100644
index 000000000..a892013f0
--- /dev/null
+++ b/convert-objects.c
@@ -0,0 +1,325 @@
+#define _XOPEN_SOURCE /* glibc2 needs this */
+#include <time.h>
+#include "cache.h"
+
+struct entry {
+ unsigned char old_sha1[20];
+ unsigned char new_sha1[20];
+ int converted;
+};
+
+#define MAXOBJECTS (1000000)
+
+static struct entry *convert[MAXOBJECTS];
+static int nr_convert;
+
+static struct entry * convert_entry(unsigned char *sha1);
+
+static struct entry *insert_new(unsigned char *sha1, int pos)
+{
+ struct entry *new = xmalloc(sizeof(struct entry));
+ memset(new, 0, sizeof(*new));
+ memcpy(new->old_sha1, sha1, 20);
+ memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
+ convert[pos] = new;
+ nr_convert++;
+ if (nr_convert == MAXOBJECTS)
+ die("you're kidding me - hit maximum object limit");
+ return new;
+}
+
+static struct entry *lookup_entry(unsigned char *sha1)
+{
+ int low = 0, high = nr_convert;
+
+ while (low < high) {
+ int next = (low + high) / 2;
+ struct entry *n = convert[next];
+ int cmp = memcmp(sha1, n->old_sha1, 20);
+ if (!cmp)
+ return n;
+ if (cmp < 0) {
+ high = next;
+ continue;
+ }
+ low = next+1;
+ }
+ return insert_new(sha1, low);
+}
+
+static void convert_binary_sha1(void *buffer)
+{
+ struct entry *entry = convert_entry(buffer);
+ memcpy(buffer, entry->new_sha1, 20);
+}
+
+static void convert_ascii_sha1(void *buffer)
+{
+ unsigned char sha1[20];
+ struct entry *entry;
+
+ if (get_sha1_hex(buffer, sha1))
+ die("expected sha1, got '%s'", (char*) buffer);
+ entry = convert_entry(sha1);
+ memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
+}
+
+static unsigned int convert_mode(unsigned int mode)
+{
+ unsigned int newmode;
+
+ newmode = mode & S_IFMT;
+ if (S_ISREG(mode))
+ newmode |= (mode & 0100) ? 0755 : 0644;
+ return newmode;
+}
+
+static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1)
+{
+ char *new = xmalloc(size);
+ unsigned long newlen = 0;
+ unsigned long used;
+
+ used = 0;
+ while (size) {
+ int len = 21 + strlen(buffer);
+ char *path = strchr(buffer, ' ');
+ unsigned char *sha1;
+ unsigned int mode;
+ char *slash, *origpath;
+
+ if (!path || sscanf(buffer, "%o", &mode) != 1)
+ die("bad tree conversion");
+ mode = convert_mode(mode);
+ path++;
+ if (memcmp(path, base, baselen))
+ break;
+ origpath = path;
+ path += baselen;
+ slash = strchr(path, '/');
+ if (!slash) {
+ newlen += sprintf(new + newlen, "%o %s", mode, path);
+ new[newlen++] = '\0';
+ memcpy(new + newlen, buffer + len - 20, 20);
+ newlen += 20;
+
+ used += len;
+ size -= len;
+ buffer += len;
+ continue;
+ }
+
+ newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path);
+ new[newlen++] = 0;
+ sha1 = (unsigned char *)(new + newlen);
+ newlen += 20;
+
+ len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1);
+
+ used += len;
+ size -= len;
+ buffer += len;
+ }
+
+ write_sha1_file(new, newlen, "tree", result_sha1);
+ free(new);
+ return used;
+}
+
+static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+ void *orig_buffer = buffer;
+ unsigned long orig_size = size;
+
+ while (size) {
+ int len = 1+strlen(buffer);
+
+ convert_binary_sha1(buffer + len);
+
+ len += 20;
+ if (len > size)
+ die("corrupt tree object");
+ size -= len;
+ buffer += len;
+ }
+
+ write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1);
+}
+
+static unsigned long parse_oldstyle_date(const char *buf)
+{
+ char c, *p;
+ char buffer[100];
+ struct tm tm;
+ const char *formats[] = {
+ "%c",
+ "%a %b %d %T",
+ "%Z",
+ "%Y",
+ " %Y",
+ NULL
+ };
+ /* We only ever did two timezones in the bad old format .. */
+ const char *timezones[] = {
+ "PDT", "PST", "CEST", NULL
+ };
+ const char **fmt = formats;
+
+ p = buffer;
+ while (isspace(c = *buf))
+ buf++;
+ while ((c = *buf++) != '\n')
+ *p++ = c;
+ *p++ = 0;
+ buf = buffer;
+ memset(&tm, 0, sizeof(tm));
+ do {
+ const char *next = strptime(buf, *fmt, &tm);
+ if (next) {
+ if (!*next)
+ return mktime(&tm);
+ buf = next;
+ } else {
+ const char **p = timezones;
+ while (isspace(*buf))
+ buf++;
+ while (*p) {
+ if (!memcmp(buf, *p, strlen(*p))) {
+ buf += strlen(*p);
+ break;
+ }
+ p++;
+ }
+ }
+ fmt++;
+ } while (*buf && *fmt);
+ printf("left: %s\n", buf);
+ return mktime(&tm);
+}
+
+static int convert_date_line(char *dst, void **buf, unsigned long *sp)
+{
+ unsigned long size = *sp;
+ char *line = *buf;
+ char *next = strchr(line, '\n');
+ char *date = strchr(line, '>');
+ int len;
+
+ if (!next || !date)
+ die("missing or bad author/committer line %s", line);
+ next++; date += 2;
+
+ *buf = next;
+ *sp = size - (next - line);
+
+ len = date - line;
+ memcpy(dst, line, len);
+ dst += len;
+
+ /* Is it already in new format? */
+ if (isdigit(*date)) {
+ int datelen = next - date;
+ memcpy(dst, date, datelen);
+ return len + datelen;
+ }
+
+ /*
+ * Hacky hacky: one of the sparse old-style commits does not have
+ * any date at all, but we can fake it by using the committer date.
+ */
+ if (*date == '\n' && strchr(next, '>'))
+ date = strchr(next, '>')+2;
+
+ return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date));
+}
+
+static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+ char *new = xmalloc(size + 100);
+ unsigned long newlen = 0;
+
+ // "tree <sha1>\n"
+ memcpy(new + newlen, buffer, 46);
+ newlen += 46;
+ buffer += 46;
+ size -= 46;
+
+ // "parent <sha1>\n"
+ while (!memcmp(buffer, "parent ", 7)) {
+ memcpy(new + newlen, buffer, 48);
+ newlen += 48;
+ buffer += 48;
+ size -= 48;
+ }
+
+ // "author xyz <xyz> date"
+ newlen += convert_date_line(new + newlen, &buffer, &size);
+ // "committer xyz <xyz> date"
+ newlen += convert_date_line(new + newlen, &buffer, &size);
+
+ // Rest
+ memcpy(new + newlen, buffer, size);
+ newlen += size;
+
+ write_sha1_file(new, newlen, "commit", result_sha1);
+ free(new);
+}
+
+static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+ void *orig_buffer = buffer;
+ unsigned long orig_size = size;
+
+ if (memcmp(buffer, "tree ", 5))
+ die("Bad commit '%s'", (char*) buffer);
+ convert_ascii_sha1(buffer+5);
+ buffer += 46; /* "tree " + "hex sha1" + "\n" */
+ while (!memcmp(buffer, "parent ", 7)) {
+ convert_ascii_sha1(buffer+7);
+ buffer += 48;
+ }
+ convert_date(orig_buffer, orig_size, result_sha1);
+}
+
+static struct entry * convert_entry(unsigned char *sha1)
+{
+ struct entry *entry = lookup_entry(sha1);
+ char type[20];
+ void *buffer, *data;
+ unsigned long size;
+
+ if (entry->converted)
+ return entry;
+ data = read_sha1_file(sha1, type, &size);
+ if (!data)
+ die("unable to read object %s", sha1_to_hex(sha1));
+
+ buffer = xmalloc(size);
+ memcpy(buffer, data, size);
+
+ if (!strcmp(type, "blob")) {
+ write_sha1_file(buffer, size, "blob", entry->new_sha1);
+ } else if (!strcmp(type, "tree"))
+ convert_tree(buffer, size, entry->new_sha1);
+ else if (!strcmp(type, "commit"))
+ convert_commit(buffer, size, entry->new_sha1);
+ else
+ die("unknown object type '%s' in %s", type, sha1_to_hex(sha1));
+ entry->converted = 1;
+ free(buffer);
+ free(data);
+ return entry;
+}
+
+int main(int argc, char **argv)
+{
+ unsigned char sha1[20];
+ struct entry *entry;
+
+ if (argc != 2 || get_sha1(argv[1], sha1))
+ usage("git-convert-objects <sha1>");
+
+ entry = convert_entry(sha1);
+ printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
+ return 0;
+}
diff --git a/copy.c b/copy.c
new file mode 100644
index 000000000..e1cd5d065
--- /dev/null
+++ b/copy.c
@@ -0,0 +1,40 @@
+#include "cache.h"
+
+int copy_fd(int ifd, int ofd)
+{
+ while (1) {
+ int len;
+ char buffer[8192];
+ char *buf = buffer;
+ len = read(ifd, buffer, sizeof(buffer));
+ if (!len)
+ break;
+ if (len < 0) {
+ int read_error;
+ if (errno == EAGAIN)
+ continue;
+ read_error = errno;
+ close(ifd);
+ return error("copy-fd: read returned %s",
+ strerror(read_error));
+ }
+ while (1) {
+ int written = write(ofd, buf, len);
+ if (written > 0) {
+ buf += written;
+ len -= written;
+ if (!len)
+ break;
+ }
+ if (!written)
+ return error("copy-fd: write returned 0");
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ return error("copy-fd: write returned %s",
+ strerror(errno));
+ }
+ }
+ close(ifd);
+ return 0;
+}
+
diff --git a/count-delta.c b/count-delta.c
new file mode 100644
index 000000000..7559ff68b
--- /dev/null
+++ b/count-delta.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ * The delta-parsing part is almost straight copy of patch-delta.c
+ * which is (C) 2005 Nicolas Pitre <nico@cam.org>.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include "delta.h"
+#include "count-delta.h"
+
+/*
+ * NOTE. We do not _interpret_ delta fully. As an approximation, we
+ * just count the number of bytes that are copied from the source, and
+ * the number of literal data bytes that are inserted.
+ *
+ * Number of bytes that are _not_ copied from the source is deletion,
+ * and number of inserted literal bytes are addition, so sum of them
+ * is the extent of damage. xdelta can express an edit that copies
+ * data inside of the destination which originally came from the
+ * source. We do not count that in the following routine, so we are
+ * undercounting the source material that remains in the final output
+ * that way.
+ */
+int count_delta(void *delta_buf, unsigned long delta_size,
+ unsigned long *src_copied, unsigned long *literal_added)
+{
+ unsigned long copied_from_source, added_literal;
+ const unsigned char *data, *top;
+ unsigned char cmd;
+ unsigned long src_size, dst_size, out;
+
+ if (delta_size < DELTA_SIZE_MIN)
+ return -1;
+
+ data = delta_buf;
+ top = delta_buf + delta_size;
+
+ src_size = get_delta_hdr_size(&data);
+ dst_size = get_delta_hdr_size(&data);
+
+ added_literal = copied_from_source = out = 0;
+ while (data < top) {
+ cmd = *data++;
+ if (cmd & 0x80) {
+ unsigned long cp_off = 0, cp_size = 0;
+ if (cmd & 0x01) cp_off = *data++;
+ if (cmd & 0x02) cp_off |= (*data++ << 8);
+ if (cmd & 0x04) cp_off |= (*data++ << 16);
+ if (cmd & 0x08) cp_off |= (*data++ << 24);
+ if (cmd & 0x10) cp_size = *data++;
+ if (cmd & 0x20) cp_size |= (*data++ << 8);
+ if (cp_size == 0) cp_size = 0x10000;
+
+ if (cmd & 0x40)
+ /* copy from dst */
+ ;
+ else
+ copied_from_source += cp_size;
+ out += cp_size;
+ } else {
+ /* write literal into dst */
+ added_literal += cmd;
+ out += cmd;
+ data += cmd;
+ }
+ }
+
+ /* sanity check */
+ if (data != top || out != dst_size)
+ return -1;
+
+ /* delete size is what was _not_ copied from source.
+ * edit size is that and literal additions.
+ */
+ *src_copied = copied_from_source;
+ *literal_added = added_literal;
+ return 0;
+}
diff --git a/count-delta.h b/count-delta.h
new file mode 100644
index 000000000..735962982
--- /dev/null
+++ b/count-delta.h
@@ -0,0 +1,10 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#ifndef COUNT_DELTA_H
+#define COUNT_DELTA_H
+
+int count_delta(void *, unsigned long,
+ unsigned long *src_copied, unsigned long *literal_added);
+
+#endif
diff --git a/csum-file.c b/csum-file.c
new file mode 100644
index 000000000..c66b9eb10
--- /dev/null
+++ b/csum-file.c
@@ -0,0 +1,148 @@
+/*
+ * csum-file.c
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ *
+ * Simple file write infrastructure for writing SHA1-summed
+ * files. Useful when you write a file that you want to be
+ * able to verify hasn't been messed with afterwards.
+ */
+#include "cache.h"
+#include "csum-file.h"
+
+static int sha1flush(struct sha1file *f, unsigned int count)
+{
+ void *buf = f->buffer;
+
+ for (;;) {
+ int ret = write(f->fd, buf, count);
+ if (ret > 0) {
+ buf += ret;
+ count -= ret;
+ if (count)
+ continue;
+ return 0;
+ }
+ if (!ret)
+ die("sha1 file '%s' write error. Out of diskspace", f->name);
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ die("sha1 file '%s' write error (%s)", f->name, strerror(errno));
+ }
+}
+
+int sha1close(struct sha1file *f, unsigned char *result, int update)
+{
+ unsigned offset = f->offset;
+ if (offset) {
+ SHA1_Update(&f->ctx, f->buffer, offset);
+ sha1flush(f, offset);
+ }
+ SHA1_Final(f->buffer, &f->ctx);
+ if (result)
+ memcpy(result, f->buffer, 20);
+ if (update)
+ sha1flush(f, 20);
+ if (close(f->fd))
+ die("%s: sha1 file error on close (%s)", f->name, strerror(errno));
+ free(f);
+ return 0;
+}
+
+int sha1write(struct sha1file *f, void *buf, unsigned int count)
+{
+ while (count) {
+ unsigned offset = f->offset;
+ unsigned left = sizeof(f->buffer) - offset;
+ unsigned nr = count > left ? left : count;
+
+ memcpy(f->buffer + offset, buf, nr);
+ count -= nr;
+ offset += nr;
+ buf += nr;
+ left -= nr;
+ if (!left) {
+ SHA1_Update(&f->ctx, f->buffer, offset);
+ sha1flush(f, offset);
+ offset = 0;
+ }
+ f->offset = offset;
+ }
+ return 0;
+}
+
+struct sha1file *sha1create(const char *fmt, ...)
+{
+ struct sha1file *f;
+ unsigned len;
+ va_list arg;
+ int fd;
+
+ f = xmalloc(sizeof(*f));
+
+ va_start(arg, fmt);
+ len = vsnprintf(f->name, sizeof(f->name), fmt, arg);
+ va_end(arg);
+ if (len >= PATH_MAX)
+ die("you wascally wabbit, you");
+ f->namelen = len;
+
+ fd = open(f->name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ if (fd < 0)
+ die("unable to open %s (%s)", f->name, strerror(errno));
+ f->fd = fd;
+ f->error = 0;
+ f->offset = 0;
+ SHA1_Init(&f->ctx);
+ return f;
+}
+
+struct sha1file *sha1fd(int fd, const char *name)
+{
+ struct sha1file *f;
+ unsigned len;
+
+ f = xmalloc(sizeof(*f));
+
+ len = strlen(name);
+ if (len >= PATH_MAX)
+ die("you wascally wabbit, you");
+ f->namelen = len;
+ memcpy(f->name, name, len+1);
+
+ f->fd = fd;
+ f->error = 0;
+ f->offset = 0;
+ SHA1_Init(&f->ctx);
+ return f;
+}
+
+int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
+{
+ z_stream stream;
+ unsigned long maxsize;
+ void *out;
+
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+ maxsize = deflateBound(&stream, size);
+ out = xmalloc(maxsize);
+
+ /* Compress it */
+ stream.next_in = in;
+ stream.avail_in = size;
+
+ stream.next_out = out;
+ stream.avail_out = maxsize;
+
+ while (deflate(&stream, Z_FINISH) == Z_OK)
+ /* nothing */;
+ deflateEnd(&stream);
+
+ size = stream.total_out;
+ sha1write(f, out, size);
+ free(out);
+ return size;
+}
+
+
diff --git a/csum-file.h b/csum-file.h
new file mode 100644
index 000000000..3ad1a992a
--- /dev/null
+++ b/csum-file.h
@@ -0,0 +1,19 @@
+#ifndef CSUM_FILE_H
+#define CSUM_FILE_H
+
+/* A SHA1-protected file */
+struct sha1file {
+ int fd, error;
+ unsigned int offset, namelen;
+ SHA_CTX ctx;
+ char name[PATH_MAX];
+ unsigned char buffer[8192];
+};
+
+extern struct sha1file *sha1fd(int fd, const char *name);
+extern struct sha1file *sha1create(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern int sha1close(struct sha1file *, unsigned char *, int);
+extern int sha1write(struct sha1file *, void *, unsigned int);
+extern int sha1write_compressed(struct sha1file *, void *, unsigned int);
+
+#endif
diff --git a/ctype.c b/ctype.c
new file mode 100644
index 000000000..56bdffa63
--- /dev/null
+++ b/ctype.c
@@ -0,0 +1,23 @@
+/*
+ * Sane locale-independent, ASCII ctype.
+ *
+ * No surprises, and works with signed and unsigned chars.
+ */
+#include "cache.h"
+
+#define SS GIT_SPACE
+#define AA GIT_ALPHA
+#define DD GIT_DIGIT
+
+unsigned char sane_ctype[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-15 */
+ SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32-15 */
+ DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, 0, /* 48-15 */
+ 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 64-15 */
+ AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 80-15 */
+ 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 96-15 */
+ AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 112-15 */
+ /* Nothing in the 128.. range */
+};
+
diff --git a/daemon.c b/daemon.c
new file mode 100644
index 000000000..2b81152d7
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,640 @@
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <syslog.h>
+#include "pkt-line.h"
+#include "cache.h"
+
+static int log_syslog;
+static int verbose;
+
+static const char daemon_usage[] =
+"git-daemon [--verbose] [--syslog] [--inetd | --port=n] [--export-all]\n"
+" [--timeout=n] [--init-timeout=n] [directory...]";
+
+/* List of acceptable pathname prefixes */
+static char **ok_paths = NULL;
+
+/* If this is set, git-daemon-export-ok is not required */
+static int export_all_trees = 0;
+
+/* Timeout, and initial timeout */
+static unsigned int timeout = 0;
+static unsigned int init_timeout = 0;
+
+static void logreport(int priority, const char *err, va_list params)
+{
+ /* We should do a single write so that it is atomic and output
+ * of several processes do not get intermingled. */
+ char buf[1024];
+ int buflen;
+ int maxlen, msglen;
+
+ /* sizeof(buf) should be big enough for "[pid] \n" */
+ buflen = snprintf(buf, sizeof(buf), "[%ld] ", (long) getpid());
+
+ maxlen = sizeof(buf) - buflen - 1; /* -1 for our own LF */
+ msglen = vsnprintf(buf + buflen, maxlen, err, params);
+
+ if (log_syslog) {
+ syslog(priority, "%s", buf);
+ return;
+ }
+
+ /* maxlen counted our own LF but also counts space given to
+ * vsnprintf for the terminating NUL. We want to make sure that
+ * we have space for our own LF and NUL after the "meat" of the
+ * message, so truncate it at maxlen - 1.
+ */
+ if (msglen > maxlen - 1)
+ msglen = maxlen - 1;
+ else if (msglen < 0)
+ msglen = 0; /* Protect against weird return values. */
+ buflen += msglen;
+
+ buf[buflen++] = '\n';
+ buf[buflen] = '\0';
+
+ write(2, buf, buflen);
+}
+
+static void logerror(const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ logreport(LOG_ERR, err, params);
+ va_end(params);
+}
+
+static void loginfo(const char *err, ...)
+{
+ va_list params;
+ if (!verbose)
+ return;
+ va_start(params, err);
+ logreport(LOG_INFO, err, params);
+ va_end(params);
+}
+
+static int path_ok(const char *dir)
+{
+ const char *p = dir;
+ char **pp;
+ int sl, ndot;
+
+ /* The pathname here should be an absolute path. */
+ if ( *p++ != '/' )
+ return 0;
+
+ sl = 1; ndot = 0;
+
+ for (;;) {
+ if ( *p == '.' ) {
+ ndot++;
+ } else if ( *p == '\0' ) {
+ /* Reject "." and ".." at the end of the path */
+ if ( sl && ndot > 0 && ndot < 3 )
+ return 0;
+
+ /* Otherwise OK */
+ break;
+ } else if ( *p == '/' ) {
+ /* Refuse "", "." or ".." */
+ if ( sl && ndot < 3 )
+ return 0;
+ sl = 1;
+ ndot = 0;
+ } else {
+ sl = ndot = 0;
+ }
+ p++;
+ }
+
+ if ( ok_paths && *ok_paths ) {
+ int ok = 0;
+ int dirlen = strlen(dir);
+
+ for ( pp = ok_paths ; *pp ; pp++ ) {
+ int len = strlen(*pp);
+ if ( len <= dirlen &&
+ !strncmp(*pp, dir, len) &&
+ (dir[len] == '/' || dir[len] == '\0') ) {
+ ok = 1;
+ break;
+ }
+ }
+
+ if ( !ok )
+ return 0; /* Path not in whitelist */
+ }
+
+ return 1; /* Path acceptable */
+}
+
+static int set_dir(const char *dir)
+{
+ if (!path_ok(dir)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ if ( chdir(dir) )
+ return -1;
+
+ /*
+ * Security on the cheap.
+ *
+ * We want a readable HEAD, usable "objects" directory, and
+ * a "git-daemon-export-ok" flag that says that the other side
+ * is ok with us doing this.
+ */
+ if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ if (access("objects/", X_OK) || access("HEAD", R_OK)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* If all this passed, we're OK */
+ return 0;
+}
+
+static int upload(char *dir)
+{
+ /* Try paths in this order */
+ static const char *paths[] = { "%s", "%s/.git", "%s.git", "%s.git/.git", NULL };
+ const char **pp;
+ /* Enough for the longest path above including final null */
+ int buflen = strlen(dir)+10;
+ char *dirbuf = xmalloc(buflen);
+ /* Timeout as string */
+ char timeout_buf[64];
+
+ loginfo("Request for '%s'", dir);
+
+ for ( pp = paths ; *pp ; pp++ ) {
+ snprintf(dirbuf, buflen, *pp, dir);
+ if ( !set_dir(dirbuf) )
+ break;
+ }
+
+ if ( !*pp ) {
+ logerror("Cannot set directory '%s': %s", dir, strerror(errno));
+ return -1;
+ }
+
+ /*
+ * We'll ignore SIGTERM from now on, we have a
+ * good client.
+ */
+ signal(SIGTERM, SIG_IGN);
+
+ snprintf(timeout_buf, sizeof timeout_buf, "--timeout=%u", timeout);
+
+ /* git-upload-pack only ever reads stuff, so this is safe */
+ execlp("git-upload-pack", "git-upload-pack", "--strict", timeout_buf, ".", NULL);
+ return -1;
+}
+
+static int execute(void)
+{
+ static char line[1000];
+ int len;
+
+ alarm(init_timeout ? init_timeout : timeout);
+ len = packet_read_line(0, line, sizeof(line));
+ alarm(0);
+
+ if (len && line[len-1] == '\n')
+ line[--len] = 0;
+
+ if (!strncmp("git-upload-pack /", line, 17))
+ return upload(line+16);
+
+ logerror("Protocol error: '%s'", line);
+ return -1;
+}
+
+
+/*
+ * We count spawned/reaped separately, just to avoid any
+ * races when updating them from signals. The SIGCHLD handler
+ * will only update children_reaped, and the fork logic will
+ * only update children_spawned.
+ *
+ * MAX_CHILDREN should be a power-of-two to make the modulus
+ * operation cheap. It should also be at least twice
+ * the maximum number of connections we will ever allow.
+ */
+#define MAX_CHILDREN 128
+
+static int max_connections = 25;
+
+/* These are updated by the signal handler */
+static volatile unsigned int children_reaped = 0;
+static pid_t dead_child[MAX_CHILDREN];
+
+/* These are updated by the main loop */
+static unsigned int children_spawned = 0;
+static unsigned int children_deleted = 0;
+
+static struct child {
+ pid_t pid;
+ int addrlen;
+ struct sockaddr_storage address;
+} live_child[MAX_CHILDREN];
+
+static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen)
+{
+ live_child[idx].pid = pid;
+ live_child[idx].addrlen = addrlen;
+ memcpy(&live_child[idx].address, addr, addrlen);
+}
+
+/*
+ * Walk from "deleted" to "spawned", and remove child "pid".
+ *
+ * We move everything up by one, since the new "deleted" will
+ * be one higher.
+ */
+static void remove_child(pid_t pid, unsigned deleted, unsigned spawned)
+{
+ struct child n;
+
+ deleted %= MAX_CHILDREN;
+ spawned %= MAX_CHILDREN;
+ if (live_child[deleted].pid == pid) {
+ live_child[deleted].pid = -1;
+ return;
+ }
+ n = live_child[deleted];
+ for (;;) {
+ struct child m;
+ deleted = (deleted + 1) % MAX_CHILDREN;
+ if (deleted == spawned)
+ die("could not find dead child %d\n", pid);
+ m = live_child[deleted];
+ live_child[deleted] = n;
+ if (m.pid == pid)
+ return;
+ n = m;
+ }
+}
+
+/*
+ * This gets called if the number of connections grows
+ * past "max_connections".
+ *
+ * We _should_ start off by searching for connections
+ * from the same IP, and if there is some address wth
+ * multiple connections, we should kill that first.
+ *
+ * As it is, we just "randomly" kill 25% of the connections,
+ * and our pseudo-random generator sucks too. I have no
+ * shame.
+ *
+ * Really, this is just a place-holder for a _real_ algorithm.
+ */
+static void kill_some_children(int signo, unsigned start, unsigned stop)
+{
+ start %= MAX_CHILDREN;
+ stop %= MAX_CHILDREN;
+ while (start != stop) {
+ if (!(start & 3))
+ kill(live_child[start].pid, signo);
+ start = (start + 1) % MAX_CHILDREN;
+ }
+}
+
+static void check_max_connections(void)
+{
+ for (;;) {
+ int active;
+ unsigned spawned, reaped, deleted;
+
+ spawned = children_spawned;
+ reaped = children_reaped;
+ deleted = children_deleted;
+
+ while (deleted < reaped) {
+ pid_t pid = dead_child[deleted % MAX_CHILDREN];
+ remove_child(pid, deleted, spawned);
+ deleted++;
+ }
+ children_deleted = deleted;
+
+ active = spawned - deleted;
+ if (active <= max_connections)
+ break;
+
+ /* Kill some unstarted connections with SIGTERM */
+ kill_some_children(SIGTERM, deleted, spawned);
+ if (active <= max_connections << 1)
+ break;
+
+ /* If the SIGTERM thing isn't helping use SIGKILL */
+ kill_some_children(SIGKILL, deleted, spawned);
+ sleep(1);
+ }
+}
+
+static void handle(int incoming, struct sockaddr *addr, int addrlen)
+{
+ pid_t pid = fork();
+ char addrbuf[256] = "";
+ int port = -1;
+
+ if (pid) {
+ unsigned idx;
+
+ close(incoming);
+ if (pid < 0)
+ return;
+
+ idx = children_spawned % MAX_CHILDREN;
+ children_spawned++;
+ add_child(idx, pid, addr, addrlen);
+
+ check_max_connections();
+ return;
+ }
+
+ dup2(incoming, 0);
+ dup2(incoming, 1);
+ close(incoming);
+
+ if (addr->sa_family == AF_INET) {
+ struct sockaddr_in *sin_addr = (void *) addr;
+ inet_ntop(AF_INET, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf));
+ port = sin_addr->sin_port;
+
+#ifndef NO_IPV6
+ } else if (addr->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6_addr = (void *) addr;
+
+ char *buf = addrbuf;
+ *buf++ = '['; *buf = '\0'; /* stpcpy() is cool */
+ inet_ntop(AF_INET6, &sin6_addr->sin6_addr, buf, sizeof(addrbuf) - 1);
+ strcat(buf, "]");
+
+ port = sin6_addr->sin6_port;
+#endif
+ }
+ loginfo("Connection from %s:%d", addrbuf, port);
+
+ exit(execute());
+}
+
+static void child_handler(int signo)
+{
+ for (;;) {
+ int status;
+ pid_t pid = waitpid(-1, &status, WNOHANG);
+
+ if (pid > 0) {
+ unsigned reaped = children_reaped;
+ dead_child[reaped % MAX_CHILDREN] = pid;
+ children_reaped = reaped + 1;
+ /* XXX: Custom logging, since we don't wanna getpid() */
+ if (verbose) {
+ char *dead = "";
+ if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+ dead = " (with error)";
+ if (log_syslog)
+ syslog(LOG_INFO, "[%d] Disconnected%s", pid, dead);
+ else
+ fprintf(stderr, "[%d] Disconnected%s\n", pid, dead);
+ }
+ continue;
+ }
+ break;
+ }
+}
+
+#ifndef NO_IPV6
+
+static int socksetup(int port, int **socklist_p)
+{
+ int socknum = 0, *socklist = NULL;
+ int maxfd = -1;
+ char pbuf[NI_MAXSERV];
+
+ struct addrinfo hints, *ai0, *ai;
+ int gai;
+
+ sprintf(pbuf, "%d", port);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+ hints.ai_flags = AI_PASSIVE;
+
+ gai = getaddrinfo(NULL, pbuf, &hints, &ai0);
+ if (gai)
+ die("getaddrinfo() failed: %s\n", gai_strerror(gai));
+
+ for (ai = ai0; ai; ai = ai->ai_next) {
+ int sockfd;
+ int *newlist;
+
+ sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (sockfd < 0)
+ continue;
+ if (sockfd >= FD_SETSIZE) {
+ error("too large socket descriptor.");
+ close(sockfd);
+ continue;
+ }
+
+#ifdef IPV6_V6ONLY
+ if (ai->ai_family == AF_INET6) {
+ int on = 1;
+ setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof(on));
+ /* Note: error is not fatal */
+ }
+#endif
+
+ if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+ close(sockfd);
+ continue; /* not fatal */
+ }
+ if (listen(sockfd, 5) < 0) {
+ close(sockfd);
+ continue; /* not fatal */
+ }
+
+ newlist = realloc(socklist, sizeof(int) * (socknum + 1));
+ if (!newlist)
+ die("memory allocation failed: %s", strerror(errno));
+
+ socklist = newlist;
+ socklist[socknum++] = sockfd;
+
+ if (maxfd < sockfd)
+ maxfd = sockfd;
+ }
+
+ freeaddrinfo(ai0);
+
+ *socklist_p = socklist;
+ return socknum;
+}
+
+#else /* NO_IPV6 */
+
+static int socksetup(int port, int **socklist_p)
+{
+ struct sockaddr_in sin;
+ int sockfd;
+
+ sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sockfd < 0)
+ return 0;
+
+ memset(&sin, 0, sizeof sin);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ sin.sin_port = htons(port);
+
+ if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
+ close(sockfd);
+ return 0;
+ }
+
+ *socklist_p = xmalloc(sizeof(int));
+ **socklist_p = sockfd;
+}
+
+#endif
+
+static int service_loop(int socknum, int *socklist)
+{
+ struct pollfd *pfd;
+ int i;
+
+ pfd = xcalloc(socknum, sizeof(struct pollfd));
+
+ for (i = 0; i < socknum; i++) {
+ pfd[i].fd = socklist[i];
+ pfd[i].events = POLLIN;
+ }
+
+ signal(SIGCHLD, child_handler);
+
+ for (;;) {
+ int i;
+
+ if (poll(pfd, socknum, -1) < 0) {
+ if (errno != EINTR) {
+ error("poll failed, resuming: %s",
+ strerror(errno));
+ sleep(1);
+ }
+ continue;
+ }
+
+ for (i = 0; i < socknum; i++) {
+ if (pfd[i].revents & POLLIN) {
+ struct sockaddr_storage ss;
+ unsigned int sslen = sizeof(ss);
+ int incoming = accept(pfd[i].fd, (struct sockaddr *)&ss, &sslen);
+ if (incoming < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ case ECONNABORTED:
+ continue;
+ default:
+ die("accept returned %s", strerror(errno));
+ }
+ }
+ handle(incoming, (struct sockaddr *)&ss, sslen);
+ }
+ }
+ }
+}
+
+static int serve(int port)
+{
+ int socknum, *socklist;
+
+ socknum = socksetup(port, &socklist);
+ if (socknum == 0)
+ die("unable to allocate any listen sockets on port %u", port);
+
+ return service_loop(socknum, socklist);
+}
+
+int main(int argc, char **argv)
+{
+ int port = DEFAULT_GIT_PORT;
+ int inetd_mode = 0;
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+
+ if (!strncmp(arg, "--port=", 7)) {
+ char *end;
+ unsigned long n;
+ n = strtoul(arg+7, &end, 0);
+ if (arg[7] && !*end) {
+ port = n;
+ continue;
+ }
+ }
+ if (!strcmp(arg, "--inetd")) {
+ inetd_mode = 1;
+ log_syslog = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--verbose")) {
+ verbose = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--syslog")) {
+ log_syslog = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--export-all")) {
+ export_all_trees = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--timeout=", 10)) {
+ timeout = atoi(arg+10);
+ continue;
+ }
+ if (!strncmp(arg, "--init-timeout=", 15)) {
+ init_timeout = atoi(arg+15);
+ continue;
+ }
+ if (!strcmp(arg, "--")) {
+ ok_paths = &argv[i+1];
+ break;
+ } else if (arg[0] != '-') {
+ ok_paths = &argv[i];
+ break;
+ }
+
+ usage(daemon_usage);
+ }
+
+ if (log_syslog)
+ openlog("git-daemon", 0, LOG_DAEMON);
+
+ if (inetd_mode) {
+ fclose(stderr); //FIXME: workaround
+ return execute();
+ }
+
+ return serve(port);
+}
diff --git a/date.c b/date.c
new file mode 100644
index 000000000..3e11500ed
--- /dev/null
+++ b/date.c
@@ -0,0 +1,646 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+
+#include <time.h>
+#include <sys/time.h>
+
+#include "cache.h"
+
+static time_t my_mktime(struct tm *tm)
+{
+ static const int mdays[] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ int year = tm->tm_year - 70;
+ int month = tm->tm_mon;
+ int day = tm->tm_mday;
+
+ if (year < 0 || year > 129) /* algo only works for 1970-2099 */
+ return -1;
+ if (month < 0 || month > 11) /* array bounds */
+ return -1;
+ if (month < 2 || (year + 2) % 4)
+ day--;
+ return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
+ tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
+}
+
+static const char *month_names[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+
+static const char *weekday_names[] = {
+ "Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
+};
+
+/*
+ * The "tz" thing is passed in as this strange "decimal parse of tz"
+ * thing, which means that tz -0100 is passed in as the integer -100,
+ * even though it means "sixty minutes off"
+ */
+const char *show_date(unsigned long time, int tz)
+{
+ struct tm *tm;
+ time_t t;
+ static char timebuf[200];
+ int minutes;
+
+ minutes = tz < 0 ? -tz : tz;
+ minutes = (minutes / 100)*60 + (minutes % 100);
+ minutes = tz < 0 ? -minutes : minutes;
+ t = time + minutes * 60;
+ tm = gmtime(&t);
+ if (!tm)
+ return NULL;
+ sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
+ weekday_names[tm->tm_wday],
+ month_names[tm->tm_mon],
+ tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tm->tm_year + 1900, tz);
+ return timebuf;
+}
+
+/*
+ * Check these. And note how it doesn't do the summer-time conversion.
+ *
+ * In my world, it's always summer, and things are probably a bit off
+ * in other ways too.
+ */
+static const struct {
+ const char *name;
+ int offset;
+ int dst;
+} timezone_names[] = {
+ { "IDLW", -12, 0, }, /* International Date Line West */
+ { "NT", -11, 0, }, /* Nome */
+ { "CAT", -10, 0, }, /* Central Alaska */
+ { "HST", -10, 0, }, /* Hawaii Standard */
+ { "HDT", -10, 1, }, /* Hawaii Daylight */
+ { "YST", -9, 0, }, /* Yukon Standard */
+ { "YDT", -9, 1, }, /* Yukon Daylight */
+ { "PST", -8, 0, }, /* Pacific Standard */
+ { "PDT", -8, 1, }, /* Pacific Daylight */
+ { "MST", -7, 0, }, /* Mountain Standard */
+ { "MDT", -7, 1, }, /* Mountain Daylight */
+ { "CST", -6, 0, }, /* Central Standard */
+ { "CDT", -6, 1, }, /* Central Daylight */
+ { "EST", -5, 0, }, /* Eastern Standard */
+ { "EDT", -5, 1, }, /* Eastern Daylight */
+ { "AST", -3, 0, }, /* Atlantic Standard */
+ { "ADT", -3, 1, }, /* Atlantic Daylight */
+ { "WAT", -1, 0, }, /* West Africa */
+
+ { "GMT", 0, 0, }, /* Greenwich Mean */
+ { "UTC", 0, 0, }, /* Universal (Coordinated) */
+
+ { "WET", 0, 0, }, /* Western European */
+ { "BST", 0, 1, }, /* British Summer */
+ { "CET", +1, 0, }, /* Central European */
+ { "MET", +1, 0, }, /* Middle European */
+ { "MEWT", +1, 0, }, /* Middle European Winter */
+ { "MEST", +1, 1, }, /* Middle European Summer */
+ { "CEST", +1, 1, }, /* Central European Summer */
+ { "MESZ", +1, 1, }, /* Middle European Summer */
+ { "FWT", +1, 0, }, /* French Winter */
+ { "FST", +1, 1, }, /* French Summer */
+ { "EET", +2, 0, }, /* Eastern Europe, USSR Zone 1 */
+ { "EEST", +2, 1, }, /* Eastern European Daylight */
+ { "WAST", +7, 0, }, /* West Australian Standard */
+ { "WADT", +7, 1, }, /* West Australian Daylight */
+ { "CCT", +8, 0, }, /* China Coast, USSR Zone 7 */
+ { "JST", +9, 0, }, /* Japan Standard, USSR Zone 8 */
+ { "EAST", +10, 0, }, /* Eastern Australian Standard */
+ { "EADT", +10, 1, }, /* Eastern Australian Daylight */
+ { "GST", +10, 0, }, /* Guam Standard, USSR Zone 9 */
+ { "NZT", +11, 0, }, /* New Zealand */
+ { "NZST", +11, 0, }, /* New Zealand Standard */
+ { "NZDT", +11, 1, }, /* New Zealand Daylight */
+ { "IDLE", +12, 0, }, /* International Date Line East */
+};
+
+#define NR_TZ (sizeof(timezone_names) / sizeof(timezone_names[0]))
+
+static int match_string(const char *date, const char *str)
+{
+ int i = 0;
+
+ for (i = 0; *date; date++, str++, i++) {
+ if (*date == *str)
+ continue;
+ if (toupper(*date) == toupper(*str))
+ continue;
+ if (!isalnum(*date))
+ break;
+ return 0;
+ }
+ return i;
+}
+
+static int skip_alpha(const char *date)
+{
+ int i = 0;
+ do {
+ i++;
+ } while (isalpha(date[i]));
+ return i;
+}
+
+/*
+* Parse month, weekday, or timezone name
+*/
+static int match_alpha(const char *date, struct tm *tm, int *offset)
+{
+ int i;
+
+ for (i = 0; i < 12; i++) {
+ int match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < 7; i++) {
+ int match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ tm->tm_wday = i;
+ return match;
+ }
+ }
+
+ for (i = 0; i < NR_TZ; i++) {
+ int match = match_string(date, timezone_names[i].name);
+ if (match >= 3) {
+ int off = timezone_names[i].offset;
+
+ /* This is bogus, but we like summer */
+ off += timezone_names[i].dst;
+
+ /* Only use the tz name offset if we don't have anything better */
+ if (*offset == -1)
+ *offset = 60*off;
+
+ return match;
+ }
+ }
+
+ if (match_string(date, "PM") == 2) {
+ if (tm->tm_hour > 0 && tm->tm_hour < 12)
+ tm->tm_hour += 12;
+ return 2;
+ }
+
+ /* BAD CRAP */
+ return skip_alpha(date);
+}
+
+static int is_date(int year, int month, int day, struct tm *tm)
+{
+ if (month > 0 && month < 13 && day > 0 && day < 32) {
+ if (year == -1) {
+ tm->tm_mon = month-1;
+ tm->tm_mday = day;
+ return 1;
+ }
+ if (year >= 1970 && year < 2100) {
+ year -= 1900;
+ } else if (year > 70 && year < 100) {
+ /* ok */
+ } else if (year < 38) {
+ year += 100;
+ } else
+ return 0;
+
+ tm->tm_mon = month-1;
+ tm->tm_mday = day;
+ tm->tm_year = year;
+ return 1;
+ }
+ return 0;
+}
+
+static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
+{
+ long num2, num3;
+
+ num2 = strtol(end+1, &end, 10);
+ num3 = -1;
+ if (*end == c && isdigit(end[1]))
+ num3 = strtol(end+1, &end, 10);
+
+ /* Time? Date? */
+ switch (c) {
+ case ':':
+ if (num3 < 0)
+ num3 = 0;
+ if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
+ tm->tm_hour = num;
+ tm->tm_min = num2;
+ tm->tm_sec = num3;
+ break;
+ }
+ return 0;
+
+ case '-':
+ case '/':
+ if (num > 70) {
+ /* yyyy-mm-dd? */
+ if (is_date(num, num2, num3, tm))
+ break;
+ /* yyyy-dd-mm? */
+ if (is_date(num, num3, num2, tm))
+ break;
+ }
+ /* mm/dd/yy ? */
+ if (is_date(num3, num2, num, tm))
+ break;
+ /* dd/mm/yy ? */
+ if (is_date(num3, num, num2, tm))
+ break;
+ return 0;
+ }
+ return end - date;
+}
+
+/*
+ * We've seen a digit. Time? Year? Date?
+ */
+static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
+{
+ int n;
+ char *end;
+ unsigned long num;
+
+ num = strtoul(date, &end, 10);
+
+ /*
+ * Seconds since 1970? We trigger on that for anything after Jan 1, 2000
+ */
+ if (num > 946684800) {
+ time_t time = num;
+ if (gmtime_r(&time, tm)) {
+ *tm_gmt = 1;
+ return end - date;
+ }
+ }
+
+ /*
+ * Check for special formats: num[:-/]num[same]num
+ */
+ switch (*end) {
+ case ':':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ int match = match_multi_number(num, *end, date, end, tm);
+ if (match)
+ return match;
+ }
+ }
+
+ /*
+ * None of the special formats? Try to guess what
+ * the number meant. We use the number of digits
+ * to make a more educated guess..
+ */
+ n = 0;
+ do {
+ n++;
+ } while (isdigit(date[n]));
+
+ /* Four-digit year or a timezone? */
+ if (n == 4) {
+ if (num <= 1200 && *offset == -1) {
+ unsigned int minutes = num % 100;
+ unsigned int hours = num / 100;
+ *offset = hours*60 + minutes;
+ } else if (num > 1900 && num < 2100)
+ tm->tm_year = num - 1900;
+ return n;
+ }
+
+ /*
+ * NOTE! We will give precedence to day-of-month over month or
+ * year numebers in the 1-12 range. So 05 is always "mday 5",
+ * unless we already have a mday..
+ *
+ * IOW, 01 Apr 05 parses as "April 1st, 2005".
+ */
+ if (num > 0 && num < 32 && tm->tm_mday < 0) {
+ tm->tm_mday = num;
+ return n;
+ }
+
+ /* Two-digit year? */
+ if (n == 2 && tm->tm_year < 0) {
+ if (num < 10 && tm->tm_mday >= 0) {
+ tm->tm_year = num + 100;
+ return n;
+ }
+ if (num >= 70) {
+ tm->tm_year = num;
+ return n;
+ }
+ }
+
+ if (num > 0 && num < 32) {
+ tm->tm_mday = num;
+ } else if (num > 1900) {
+ tm->tm_year = num - 1900;
+ } else if (num > 70) {
+ tm->tm_year = num;
+ } else if (num > 0 && num < 13) {
+ tm->tm_mon = num-1;
+ }
+
+ return n;
+}
+
+static int match_tz(const char *date, int *offp)
+{
+ char *end;
+ int offset = strtoul(date+1, &end, 10);
+ int min, hour;
+ int n = end - date - 1;
+
+ min = offset % 100;
+ hour = offset / 100;
+
+ /*
+ * Don't accept any random crap.. At least 3 digits, and
+ * a valid minute. We might want to check that the minutes
+ * are divisible by 30 or something too.
+ */
+ if (min < 60 && n > 2) {
+ offset = hour*60+min;
+ if (*date == '-')
+ offset = -offset;
+
+ *offp = offset;
+ }
+ return end - date;
+}
+
+static int date_string(unsigned long date, int offset, char *buf, int len)
+{
+ int sign = '+';
+
+ if (offset < 0) {
+ offset = -offset;
+ sign = '-';
+ }
+ return snprintf(buf, len, "%lu %c%02d%02d", date, sign, offset / 60, offset % 60);
+}
+
+/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
+ (i.e. English) day/month names, and it doesn't work correctly with %z. */
+int parse_date(const char *date, char *result, int maxlen)
+{
+ struct tm tm;
+ int offset, tm_gmt;
+ time_t then;
+
+ memset(&tm, 0, sizeof(tm));
+ tm.tm_year = -1;
+ tm.tm_mon = -1;
+ tm.tm_mday = -1;
+ tm.tm_isdst = -1;
+ offset = -1;
+ tm_gmt = 0;
+
+ for (;;) {
+ int match = 0;
+ unsigned char c = *date;
+
+ /* Stop at end of string or newline */
+ if (!c || c == '\n')
+ break;
+
+ if (isalpha(c))
+ match = match_alpha(date, &tm, &offset);
+ else if (isdigit(c))
+ match = match_digit(date, &tm, &offset, &tm_gmt);
+ else if ((c == '-' || c == '+') && isdigit(date[1]))
+ match = match_tz(date, &offset);
+
+ if (!match) {
+ /* BAD CRAP */
+ match = 1;
+ }
+
+ date += match;
+ }
+
+ /* mktime uses local timezone */
+ then = my_mktime(&tm);
+ if (offset == -1)
+ offset = (then - mktime(&tm)) / 60;
+
+ if (then == -1)
+ return -1;
+
+ if (!tm_gmt)
+ then -= offset * 60;
+ return date_string(then, offset, result, maxlen);
+}
+
+void datestamp(char *buf, int bufsize)
+{
+ time_t now;
+ int offset;
+
+ time(&now);
+
+ offset = my_mktime(localtime(&now)) - now;
+ offset /= 60;
+
+ date_string(now, offset, buf, bufsize);
+}
+
+static void update_tm(struct tm *tm, unsigned long sec)
+{
+ time_t n = mktime(tm) - sec;
+ localtime_r(&n, tm);
+}
+
+static void date_yesterday(struct tm *tm, int *num)
+{
+ update_tm(tm, 24*60*60);
+}
+
+static void date_time(struct tm *tm, int hour)
+{
+ if (tm->tm_hour < hour)
+ date_yesterday(tm, NULL);
+ tm->tm_hour = hour;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+}
+
+static void date_midnight(struct tm *tm, int *num)
+{
+ date_time(tm, 0);
+}
+
+static void date_noon(struct tm *tm, int *num)
+{
+ date_time(tm, 12);
+}
+
+static void date_tea(struct tm *tm, int *num)
+{
+ date_time(tm, 17);
+}
+
+static const struct special {
+ const char *name;
+ void (*fn)(struct tm *, int *);
+} special[] = {
+ { "yesterday", date_yesterday },
+ { "noon", date_noon },
+ { "midnight", date_midnight },
+ { "tea", date_tea },
+ { NULL }
+};
+
+static const char *number_name[] = {
+ "zero", "one", "two", "three", "four",
+ "five", "six", "seven", "eight", "nine", "ten",
+};
+
+static const struct typelen {
+ const char *type;
+ int length;
+} typelen[] = {
+ { "seconds", 1 },
+ { "minutes", 60 },
+ { "hours", 60*60 },
+ { "days", 24*60*60 },
+ { "weeks", 7*24*60*60 },
+ { NULL }
+};
+
+static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
+{
+ const struct typelen *tl;
+ const struct special *s;
+ const char *end = date;
+ int n = 1, i;
+
+ while (isalpha(*++end))
+ n++;
+
+ for (i = 0; i < 12; i++) {
+ int match = match_string(date, month_names[i]);
+ if (match >= 3) {
+ tm->tm_mon = i;
+ return end;
+ }
+ }
+
+ for (s = special; s->name; s++) {
+ int len = strlen(s->name);
+ if (match_string(date, s->name) == len) {
+ s->fn(tm, num);
+ return end;
+ }
+ }
+
+ if (!*num) {
+ for (i = 1; i < 11; i++) {
+ int len = strlen(number_name[i]);
+ if (match_string(date, number_name[i]) == len) {
+ *num = i;
+ return end;
+ }
+ }
+ if (match_string(date, "last") == 4)
+ *num = 1;
+ return end;
+ }
+
+ tl = typelen;
+ while (tl->type) {
+ int len = strlen(tl->type);
+ if (match_string(date, tl->type) >= len-1) {
+ update_tm(tm, tl->length * *num);
+ *num = 0;
+ return end;
+ }
+ tl++;
+ }
+
+ for (i = 0; i < 7; i++) {
+ int match = match_string(date, weekday_names[i]);
+ if (match >= 3) {
+ int diff, n = *num -1;
+ *num = 0;
+
+ diff = tm->tm_wday - i;
+ if (diff <= 0)
+ n++;
+ diff += 7*n;
+
+ update_tm(tm, diff * 24 * 60 * 60);
+ return end;
+ }
+ }
+
+ if (match_string(date, "months") >= 5) {
+ int n = tm->tm_mon - *num;
+ *num = 0;
+ while (n < 0) {
+ n += 12;
+ tm->tm_year--;
+ }
+ tm->tm_mon = n;
+ return end;
+ }
+
+ if (match_string(date, "years") >= 4) {
+ tm->tm_year -= *num;
+ *num = 0;
+ return end;
+ }
+
+ return end;
+}
+
+unsigned long approxidate(const char *date)
+{
+ int number = 0;
+ struct tm tm, now;
+ struct timeval tv;
+ char buffer[50];
+
+ if (parse_date(date, buffer, sizeof(buffer)) > 0)
+ return strtoul(buffer, NULL, 10);
+
+ gettimeofday(&tv, NULL);
+ localtime_r(&tv.tv_sec, &tm);
+ now = tm;
+ for (;;) {
+ unsigned char c = *date;
+ if (!c)
+ break;
+ date++;
+ if (isdigit(c)) {
+ char *end;
+ number = strtoul(date-1, &end, 10);
+ date = end;
+ continue;
+ }
+ if (isalpha(c))
+ date = approxidate_alpha(date-1, &tm, &number);
+ }
+ if (number > 0 && number < 32)
+ tm.tm_mday = number;
+ if (tm.tm_mon > now.tm_mon)
+ tm.tm_year--;
+ return mktime(&tm);
+}
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644
index 000000000..335ce9c59
--- /dev/null
+++ b/debian/.gitignore
@@ -0,0 +1,6 @@
+git-core
+git-tk
+*.debhelper
+*.substvars
+build-stamp
+files
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 000000000..376f0fa5a
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,111 @@
+git-core (0.99.9.GIT-2) unstable; urgency=low
+
+ * Build Dependency did not include libexpat-dev.
+
+ -- Junio C Hamano <junkio@cox.net> Sun, 13 Nov 2005 01:55:34 -0800
+
+git-core (0.99.9.GIT-1) unstable; urgency=low
+
+ * Do not scatter txt and html documentation into feature
+ subpackages. Do place man pages into them.
+ * Capture more cvs stuff into git-cvs package.
+
+ -- Junio C Hamano <junkio@cox.net> Tue, 8 Nov 2005 01:19:06 -0800
+
+git-core (0.99.9.GIT-0) unstable; urgency=low
+
+ * Test Build.
+
+ -- Junio C Hamano <junkio@cox.net> Sat, 5 Nov 2005 11:18:13 -0800
+
+git-core (0.99.9-1) unstable; urgency=low
+
+ * Split the git-core binary package into core, doc, and foreign SCM
+ interoperability modules.
+
+ -- Junio C Hamano <junkio@cox.net> Sat, 5 Nov 2005 11:18:13 -0800
+
+git-core (0.99.9-0) unstable; urgency=low
+
+ * GIT 0.99.9
+
+ -- Junio C Hamano <junkio@cox.net> Sat, 29 Oct 2005 14:34:30 -0700
+
+git-core (0.99.8-0) unstable; urgency=low
+
+ * GIT 0.99.8
+
+ -- Junio C Hamano <junkio@cox.net> Sun, 2 Oct 2005 12:54:26 -0700
+
+git-core (0.99.7-0) unstable; urgency=low
+
+ * GIT 0.99.7
+
+ -- Junio C Hamano <junkio@cox.net> Sat, 10 Sep 2005 18:36:39 -0700
+
+git-core (0.99.6-0) unstable; urgency=low
+
+ * GIT 0.99.6
+
+ -- Junio C Hamano <junkio@cox.net> Wed, 24 Aug 2005 23:09:35 -0700
+
+git-core (0.99.5-1) unstable; urgency=low
+
+ * Enable git-send-email on Debian. There is no reason to shy
+ away from it, since we have the necessary Perl modules available.
+
+ -- Junio C Hamano <junkio@cox.net> Thu, 25 Aug 2005 14:16:59 -0700
+
+git-core (0.99.5-0) unstable; urgency=low
+
+ * GIT 0.99.5
+
+ -- Junio C Hamano <junkio@cox.net> Wed, 10 Aug 2005 22:05:00 -0700
+
+git-core (0.99.4-4) unstable; urgency=low
+
+ * Mark git-tk as architecture neutral.
+
+ -- Junio C Hamano <junkio@cox.net> Fri, 12 Aug 2005 13:25:00 -0700
+
+git-core (0.99.4-3) unstable; urgency=low
+
+ * Split off gitk.
+ * Do not depend on diff which is an essential package.
+ * Use dh_movefiles, not dh_install, to stage two subpackages.
+
+ -- Matthias Urlichs <smurf@debian.org> Thu, 11 Aug 2005 01:43:24 +0200
+
+git-core (0.99.4-2) unstable; urgency=low
+
+ * Git 0.99.4 official release.
+
+ -- Junio C Hamano <junkio@cox.net> Wed, 10 Aug 2005 15:00:00 -0700
+
+git-core (0.99.4-1) unstable; urgency=low
+
+ * Pass prefix down to the submake when building.
+
+ -- Junio C Hamano <junkio@cox.net> Sat, 6 Aug 2005 13:00:00 -0700
+
+git-core (0.99-2) unstable; urgency=low
+
+ * Conflict with the GNU Interactive Tools package, which also installs
+ /usr/bin/git.
+ * Use the Mozilla SHA1 code and/or the PPC assembly in preference to
+ OpenSSL. This is only a partial fix for the license issues with OpenSSL.
+ * Minor tweaks to the Depends.
+
+ -- Ryan Anderson <ryan@michonline.com> Sat, 23 Jul 2005 14:15:00 -0400
+
+git-core (0.99-1) unstable; urgency=low
+
+ * Update deb package support to build correctly.
+
+ -- Ryan Anderson <ryan@michonline.com> Thu, 21 Jul 2005 02:03:32 -0400
+
+git-core (0.99-0) unstable; urgency=low
+
+ * Initial deb package support
+
+ -- Eric Biederman <ebiederm@xmission.com> Tue, 12 Jul 2005 10:57:51 -0600
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 000000000..b8626c4cf
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+4
diff --git a/debian/control b/debian/control
new file mode 100644
index 000000000..ded0a576d
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,63 @@
+Source: git-core
+Section: devel
+Priority: optional
+Maintainer: Junio C Hamano <junkio@cox.net>
+Build-Depends-Indep: libz-dev, libssl-dev, libcurl3-dev|libcurl3-gnutls-dev|libcurl3-openssl-dev, asciidoc (>= 7), xmlto, debhelper (>= 4.0.0), bc, libexpat-dev
+Standards-Version: 3.6.1
+
+Package: git-core
+Architecture: any
+Depends: ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}, rcs
+Recommends: rsync, curl, ssh, python (>= 2.4.0), less
+Suggests: cogito, patch
+Conflicts: git, cogito (<< 0.13)
+Description: The git content addressable filesystem
+ GIT comes in two layers. The bottom layer is merely an extremely fast
+ and flexible filesystem-based database designed to store directory trees
+ with regard to their history. The top layer is a SCM-like tool which
+ enables human beings to work with the database in a manner to a degree
+ similar to other SCM tools.
+
+Package: git-doc
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, git-core
+Description: The git content addressable filesystem, Documentation
+ This package contains documentation for GIT.
+
+Package: git-tk
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, git-core, tk8.4
+Description: The git content addressable filesystem, GUI add-on
+ This package contains 'gitk', the git revision tree visualizer.
+
+Package: git-svn
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, git-core, libsvn-core-perl (>= 1.2.1)
+Suggests: subversion
+Description: The git content addressable filesystem, SVN interoperability
+ This package contains 'git-svnimport', to import development history from
+ SVN repositories.
+
+Package: git-arch
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, git-core
+Suggests: tla, bazaar
+Description: The git content addressable filesystem, GNUArch interoperability
+ This package contains 'git-archimport', to import development history from
+ GNUArch repositories.
+
+Package: git-cvs
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}, git-core, cvsps (>= 2.1)
+Suggests: cvs
+Description: The git content addressable filesystem, CVS interoperability
+ This package contains 'git-cvsimport', to import development history from
+ CVS repositories.
+
+Package: git-email
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, git-core, libmail-sendmail-perl, libemail-valid-perl
+Description: The git content addressable filesystem, e-mail add-on
+ This package contains 'git-send-email', to send a series of patch e-mails.
+
+
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 000000000..ea61effc4
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,24 @@
+This package was downloaded from ftp.kernel.org:/pub/software/scm/git/.
+
+Upstream Author: Linus Torvalds and many others
+
+Copyright:
+
+ Copyright 2005, Linus Torvalds and others.
+
+ This package is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991.
+
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this package; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ 02111-1307, USA.
+
+On Debian GNU/Linux systems, the complete text of the GNU General
+Public License can be found in `/usr/share/common-licenses/GPL'.
diff --git a/debian/docs b/debian/docs
new file mode 100644
index 000000000..e845566c0
--- /dev/null
+++ b/debian/docs
@@ -0,0 +1 @@
+README
diff --git a/debian/git-arch.files b/debian/git-arch.files
new file mode 100644
index 000000000..d7449540c
--- /dev/null
+++ b/debian/git-arch.files
@@ -0,0 +1,2 @@
+/usr/bin/git-arch*
+/usr/share/man/*/git-arch*
diff --git a/debian/git-core.doc-base b/debian/git-core.doc-base
new file mode 100644
index 000000000..eff1a9523
--- /dev/null
+++ b/debian/git-core.doc-base
@@ -0,0 +1,13 @@
+Document: git-core
+Title: git reference
+Abstract: This manual describes git
+Section: Devel
+
+Format: HTML
+Index: /usr/share/doc/git-core/git.html
+Files: /usr/share/doc/git-core/*.html
+ /usr/share/doc/git-core/*/*.html
+
+Format: text
+Files: /usr/share/doc/git-core/*.txt
+ /usr/share/doc/git-core/*/*.txt
diff --git a/debian/git-core.files b/debian/git-core.files
new file mode 100644
index 000000000..74e4e23b0
--- /dev/null
+++ b/debian/git-core.files
@@ -0,0 +1 @@
+/usr
diff --git a/debian/git-cvs.files b/debian/git-cvs.files
new file mode 100644
index 000000000..a6b40ff9e
--- /dev/null
+++ b/debian/git-cvs.files
@@ -0,0 +1,2 @@
+/usr/bin/git-cvs*
+/usr/share/man/*/git-cvs*
diff --git a/debian/git-doc.files b/debian/git-doc.files
new file mode 100644
index 000000000..0daf545ad
--- /dev/null
+++ b/debian/git-doc.files
@@ -0,0 +1,4 @@
+/usr/share/doc/git-core/*.txt
+/usr/share/doc/git-core/*.html
+/usr/share/doc/git-core/*/*.html
+/usr/share/doc/git-core/*/*.txt
diff --git a/debian/git-email.files b/debian/git-email.files
new file mode 100644
index 000000000..2d6a51fc3
--- /dev/null
+++ b/debian/git-email.files
@@ -0,0 +1,2 @@
+/usr/bin/git-send-email
+/usr/share/man/*/git-send-email.*
diff --git a/debian/git-svn.files b/debian/git-svn.files
new file mode 100644
index 000000000..eea8d83f6
--- /dev/null
+++ b/debian/git-svn.files
@@ -0,0 +1,2 @@
+/usr/bin/git-svn*
+/usr/share/man/*/git-svn*
diff --git a/debian/git-tk.files b/debian/git-tk.files
new file mode 100644
index 000000000..478ec9440
--- /dev/null
+++ b/debian/git-tk.files
@@ -0,0 +1,2 @@
+/usr/bin/gitk
+/usr/share/man/man1/gitk.*
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 000000000..4ab221ce9
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,109 @@
+#!/usr/bin/make -f
+# -*- makefile -*-
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+CFLAGS = -g -Wall
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+ CFLAGS += -O0
+else
+ CFLAGS += -O2
+endif
+export CFLAGS
+
+#
+# On PowerPC we compile against the hand-crafted assembly, on all
+# other architectures we compile against GPL'ed sha1 code lifted
+# from Mozilla. OpenSSL is strangely licensed and best avoided
+# in Debian.
+#
+HOST_ARCH=$(shell dpkg-architecture -qDEB_HOST_ARCH)
+ifeq (${HOST_ARCH},powerpc)
+ export PPC_SHA1=YesPlease
+else
+ export MOZILLA_SHA1=YesPlease
+endif
+
+# We do have the requisite perl modules in the mainline, and
+# have no reason to shy away from this script.
+export WITH_SEND_EMAIL=YesPlease
+
+PREFIX := /usr
+MANDIR := /usr/share/man/
+
+SRC := ./
+DOC := Documentation/
+DESTDIR := $(CURDIR)/debian/tmp
+DOC_DESTDIR := $(DESTDIR)/usr/share/doc/git-core/
+MAN_DESTDIR := $(DESTDIR)/$(MANDIR)
+
+build: debian/build-stamp
+debian/build-stamp:
+ dh_testdir
+ $(MAKE) prefix=$(PREFIX) PYTHON_PATH=/usr/bin/python2.4 all test doc
+ touch debian/build-stamp
+
+debian-clean:
+ dh_testdir
+ dh_testroot
+ rm -f debian/build-stamp
+ dh_clean
+
+clean: debian-clean
+ $(MAKE) clean
+
+install: build
+ dh_testdir
+ dh_testroot
+ dh_clean -k
+ dh_installdirs
+
+ make DESTDIR=$(DESTDIR) prefix=$(PREFIX) mandir=$(MANDIR) \
+ install install-doc
+
+ make -C Documentation DESTDIR=$(DESTDIR) prefix=$(PREFIX) \
+ WEBDOC_DEST=$(DOC_DESTDIR) install-webdoc
+
+ dh_movefiles -p git-arch
+ dh_movefiles -p git-cvs
+ dh_movefiles -p git-svn
+ dh_movefiles -p git-tk
+ dh_movefiles -p git-email
+ dh_movefiles -p git-doc
+ dh_movefiles -p git-core
+ find debian/tmp -type d -o -print | sed -e 's/^/? /'
+
+binary-arch: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs -a
+ dh_installdocs -a
+ dh_strip -a
+ dh_compress -a
+ dh_fixperms -a
+ dh_perl -a
+ dh_makeshlibs -a
+ dh_installdeb -a
+ dh_shlibdeps -a
+ dh_gencontrol -a
+ dh_md5sums -a
+ dh_builddeb -a
+
+binary-indep: build install
+ dh_testdir
+ dh_testroot
+ dh_installchangelogs -i
+ dh_installdocs -i
+ dh_compress -i
+ dh_fixperms -i
+ dh_makeshlibs -i
+ dh_installdeb -i
+ dh_shlibdeps -i
+ dh_gencontrol -i
+ dh_md5sums -i
+ dh_builddeb -i
+
+binary: binary-arch binary-indep
+
+.PHONY: build clean binary install clean debian-clean
diff --git a/delta.h b/delta.h
new file mode 100644
index 000000000..31d1820f8
--- /dev/null
+++ b/delta.h
@@ -0,0 +1,34 @@
+#ifndef DELTA_H
+#define DELTA_H
+
+/* handling of delta buffers */
+extern void *diff_delta(void *from_buf, unsigned long from_size,
+ void *to_buf, unsigned long to_size,
+ unsigned long *delta_size, unsigned long max_size);
+extern void *patch_delta(void *src_buf, unsigned long src_size,
+ void *delta_buf, unsigned long delta_size,
+ unsigned long *dst_size);
+
+/* the smallest possible delta size is 4 bytes */
+#define DELTA_SIZE_MIN 4
+
+/*
+ * This must be called twice on the delta data buffer, first to get the
+ * expected reference buffer size, and again to get the result buffer size.
+ */
+static inline unsigned long get_delta_hdr_size(const unsigned char **datap)
+{
+ const unsigned char *data = *datap;
+ unsigned char cmd = *data++;
+ unsigned long size = cmd & ~0x80;
+ int i = 7;
+ while (cmd & 0x80) {
+ cmd = *data++;
+ size |= (cmd & ~0x80) << i;
+ i += 7;
+ }
+ *datap = data;
+ return size;
+}
+
+#endif
diff --git a/diff-delta.c b/diff-delta.c
new file mode 100644
index 000000000..b2ae7b5e6
--- /dev/null
+++ b/diff-delta.c
@@ -0,0 +1,334 @@
+/*
+ * diff-delta.c: generate a delta between two buffers
+ *
+ * Many parts of this file have been lifted from LibXDiff version 0.10.
+ * http://www.xmailserver.org/xdiff-lib.html
+ *
+ * LibXDiff was written by Davide Libenzi <davidel@xmailserver.org>
+ * Copyright (C) 2003 Davide Libenzi
+ *
+ * Many mods for GIT usage by Nicolas Pitre <nico@cam.org>, (C) 2005.
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Use of this within git automatically means that the LGPL
+ * licensing gets turned into GPLv2 within this project.
+ */
+
+#include <stdlib.h>
+#include "delta.h"
+
+
+/* block size: min = 16, max = 64k, power of 2 */
+#define BLK_SIZE 16
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#define GR_PRIME 0x9e370001
+#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b)))
+
+/* largest prime smaller than 65536 */
+#define BASE 65521
+
+/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+#define NMAX 5552
+
+#define DO1(buf, i) { s1 += buf[i]; s2 += s1; }
+#define DO2(buf, i) DO1(buf, i); DO1(buf, i + 1);
+#define DO4(buf, i) DO2(buf, i); DO2(buf, i + 2);
+#define DO8(buf, i) DO4(buf, i); DO4(buf, i + 4);
+#define DO16(buf) DO8(buf, 0); DO8(buf, 8);
+
+static unsigned int adler32(unsigned int adler, const unsigned char *buf, int len)
+{
+ int k;
+ unsigned int s1 = adler & 0xffff;
+ unsigned int s2 = adler >> 16;
+
+ while (len > 0) {
+ k = MIN(len, NMAX);
+ len -= k;
+ while (k >= 16) {
+ DO16(buf);
+ buf += 16;
+ k -= 16;
+ }
+ if (k != 0)
+ do {
+ s1 += *buf++;
+ s2 += s1;
+ } while (--k);
+ s1 %= BASE;
+ s2 %= BASE;
+ }
+
+ return (s2 << 16) | s1;
+}
+
+static unsigned int hashbits(unsigned int size)
+{
+ unsigned int val = 1, bits = 0;
+ while (val < size && bits < 32) {
+ val <<= 1;
+ bits++;
+ }
+ return bits ? bits: 1;
+}
+
+typedef struct s_chanode {
+ struct s_chanode *next;
+ int icurr;
+} chanode_t;
+
+typedef struct s_chastore {
+ chanode_t *head, *tail;
+ int isize, nsize;
+ chanode_t *ancur;
+ chanode_t *sncur;
+ int scurr;
+} chastore_t;
+
+static void cha_init(chastore_t *cha, int isize, int icount)
+{
+ cha->head = cha->tail = NULL;
+ cha->isize = isize;
+ cha->nsize = icount * isize;
+ cha->ancur = cha->sncur = NULL;
+ cha->scurr = 0;
+}
+
+static void *cha_alloc(chastore_t *cha)
+{
+ chanode_t *ancur;
+ void *data;
+
+ ancur = cha->ancur;
+ if (!ancur || ancur->icurr == cha->nsize) {
+ ancur = malloc(sizeof(chanode_t) + cha->nsize);
+ if (!ancur)
+ return NULL;
+ ancur->icurr = 0;
+ ancur->next = NULL;
+ if (cha->tail)
+ cha->tail->next = ancur;
+ if (!cha->head)
+ cha->head = ancur;
+ cha->tail = ancur;
+ cha->ancur = ancur;
+ }
+
+ data = (void *)ancur + sizeof(chanode_t) + ancur->icurr;
+ ancur->icurr += cha->isize;
+ return data;
+}
+
+static void cha_free(chastore_t *cha)
+{
+ chanode_t *cur = cha->head;
+ while (cur) {
+ chanode_t *tmp = cur;
+ cur = cur->next;
+ free(tmp);
+ }
+}
+
+typedef struct s_bdrecord {
+ struct s_bdrecord *next;
+ unsigned int fp;
+ const unsigned char *ptr;
+} bdrecord_t;
+
+typedef struct s_bdfile {
+ const unsigned char *data, *top;
+ chastore_t cha;
+ unsigned int fphbits;
+ bdrecord_t **fphash;
+} bdfile_t;
+
+static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf)
+{
+ unsigned int fphbits;
+ int i, hsize;
+ const unsigned char *base, *data, *top;
+ bdrecord_t *brec;
+ bdrecord_t **fphash;
+
+ fphbits = hashbits(bufsize / BLK_SIZE + 1);
+ hsize = 1 << fphbits;
+ fphash = malloc(hsize * sizeof(bdrecord_t *));
+ if (!fphash)
+ return -1;
+ for (i = 0; i < hsize; i++)
+ fphash[i] = NULL;
+ cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1);
+
+ bdf->data = data = base = buf;
+ bdf->top = top = buf + bufsize;
+ data += (bufsize / BLK_SIZE) * BLK_SIZE;
+ if (data == top)
+ data -= BLK_SIZE;
+
+ for ( ; data >= base; data -= BLK_SIZE) {
+ brec = cha_alloc(&bdf->cha);
+ if (!brec) {
+ cha_free(&bdf->cha);
+ free(fphash);
+ return -1;
+ }
+ brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data));
+ brec->ptr = data;
+ i = HASH(brec->fp, fphbits);
+ brec->next = fphash[i];
+ fphash[i] = brec;
+ }
+
+ bdf->fphbits = fphbits;
+ bdf->fphash = fphash;
+
+ return 0;
+}
+
+static void delta_cleanup(bdfile_t *bdf)
+{
+ free(bdf->fphash);
+ cha_free(&bdf->cha);
+}
+
+#define COPYOP_SIZE(o, s) \
+ (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
+ !!(s & 0xff) + !!(s & 0xff00) + 1)
+
+void *diff_delta(void *from_buf, unsigned long from_size,
+ void *to_buf, unsigned long to_size,
+ unsigned long *delta_size,
+ unsigned long max_size)
+{
+ int i, outpos, outsize, inscnt, csize, msize, moff;
+ unsigned int fp;
+ const unsigned char *data, *top, *ptr1, *ptr2;
+ unsigned char *out, *orig;
+ bdrecord_t *brec;
+ bdfile_t bdf;
+
+ if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf))
+ return NULL;
+
+ outpos = 0;
+ outsize = 8192;
+ out = malloc(outsize);
+ if (!out) {
+ delta_cleanup(&bdf);
+ return NULL;
+ }
+
+ data = to_buf;
+ top = to_buf + to_size;
+
+ /* store reference buffer size */
+ out[outpos++] = from_size;
+ from_size >>= 7;
+ while (from_size) {
+ out[outpos - 1] |= 0x80;
+ out[outpos++] = from_size;
+ from_size >>= 7;
+ }
+
+ /* store target buffer size */
+ out[outpos++] = to_size;
+ to_size >>= 7;
+ while (to_size) {
+ out[outpos - 1] |= 0x80;
+ out[outpos++] = to_size;
+ to_size >>= 7;
+ }
+
+ inscnt = 0;
+ moff = 0;
+ while (data < top) {
+ msize = 0;
+ fp = adler32(0, data, MIN(top - data, BLK_SIZE));
+ i = HASH(fp, bdf.fphbits);
+ for (brec = bdf.fphash[i]; brec; brec = brec->next) {
+ if (brec->fp == fp) {
+ csize = bdf.top - brec->ptr;
+ if (csize > top - data)
+ csize = top - data;
+ for (ptr1 = brec->ptr, ptr2 = data;
+ csize && *ptr1 == *ptr2;
+ csize--, ptr1++, ptr2++);
+
+ csize = ptr1 - brec->ptr;
+ if (csize > msize) {
+ moff = brec->ptr - bdf.data;
+ msize = csize;
+ if (msize >= 0x10000) {
+ msize = 0x10000;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!msize || msize < COPYOP_SIZE(moff, msize)) {
+ if (!inscnt)
+ outpos++;
+ out[outpos++] = *data++;
+ inscnt++;
+ if (inscnt == 0x7f) {
+ out[outpos - inscnt - 1] = inscnt;
+ inscnt = 0;
+ }
+ } else {
+ if (inscnt) {
+ out[outpos - inscnt - 1] = inscnt;
+ inscnt = 0;
+ }
+
+ data += msize;
+ orig = out + outpos++;
+ i = 0x80;
+
+ if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
+ moff >>= 8;
+ if (moff & 0xff) { out[outpos++] = moff; i |= 0x02; }
+ moff >>= 8;
+ if (moff & 0xff) { out[outpos++] = moff; i |= 0x04; }
+ moff >>= 8;
+ if (moff & 0xff) { out[outpos++] = moff; i |= 0x08; }
+
+ if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
+ msize >>= 8;
+ if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
+
+ *orig = i;
+ }
+
+ if (max_size && outpos > max_size) {
+ free(out);
+ delta_cleanup(&bdf);
+ return NULL;
+ }
+
+ /* next time around the largest possible output is 1 + 4 + 3 */
+ if (outpos > outsize - 8) {
+ void *tmp = out;
+ outsize = outsize * 3 / 2;
+ out = realloc(out, outsize);
+ if (!out) {
+ free(tmp);
+ delta_cleanup(&bdf);
+ return NULL;
+ }
+ }
+ }
+
+ if (inscnt)
+ out[outpos - inscnt - 1] = inscnt;
+
+ delta_cleanup(&bdf);
+ *delta_size = outpos;
+ return out;
+}
diff --git a/diff-files.c b/diff-files.c
new file mode 100644
index 000000000..17899390b
--- /dev/null
+++ b/diff-files.c
@@ -0,0 +1,132 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "diff.h"
+
+static const char diff_files_usage[] =
+"git-diff-files [-q] "
+"[<common diff options>] [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+static struct diff_options diff_options;
+static int silent = 0;
+
+static void show_unmerge(const char *path)
+{
+ diff_unmerge(&diff_options, path);
+}
+
+static void show_file(int pfx, struct cache_entry *ce)
+{
+ diff_addremove(&diff_options, pfx, ntohl(ce->ce_mode),
+ ce->sha1, ce->name, NULL);
+}
+
+static void show_modified(int oldmode, int mode,
+ const unsigned char *old_sha1, const unsigned char *sha1,
+ char *path)
+{
+ diff_change(&diff_options, oldmode, mode, old_sha1, sha1, path, NULL);
+}
+
+int main(int argc, const char **argv)
+{
+ const char **pathspec;
+ const char *prefix = setup_git_directory();
+ int entries, i;
+
+ git_config(git_default_config);
+ diff_setup(&diff_options);
+ while (1 < argc && argv[1][0] == '-') {
+ if (!strcmp(argv[1], "--")) {
+ argv++;
+ argc--;
+ break;
+ }
+ if (!strcmp(argv[1], "-q"))
+ silent = 1;
+ else if (!strcmp(argv[1], "-r"))
+ ; /* no-op */
+ else if (!strcmp(argv[1], "-s"))
+ ; /* no-op */
+ else {
+ int diff_opt_cnt;
+ diff_opt_cnt = diff_opt_parse(&diff_options,
+ argv+1, argc-1);
+ if (diff_opt_cnt < 0)
+ usage(diff_files_usage);
+ else if (diff_opt_cnt) {
+ argv += diff_opt_cnt;
+ argc -= diff_opt_cnt;
+ continue;
+ }
+ else
+ usage(diff_files_usage);
+ }
+ argv++; argc--;
+ }
+
+ /* Find the directory, and set up the pathspec */
+ pathspec = get_pathspec(prefix, argv + 1);
+ entries = read_cache();
+
+ if (diff_setup_done(&diff_options) < 0)
+ usage(diff_files_usage);
+
+ /* At this point, if argc == 1, then we are doing everything.
+ * Otherwise argv[1] .. argv[argc-1] have the explicit paths.
+ */
+ if (entries < 0) {
+ perror("read_cache");
+ exit(1);
+ }
+
+ for (i = 0; i < entries; i++) {
+ struct stat st;
+ unsigned int oldmode, newmode;
+ struct cache_entry *ce = active_cache[i];
+ int changed;
+
+ if (!ce_path_match(ce, pathspec))
+ continue;
+
+ if (ce_stage(ce)) {
+ show_unmerge(ce->name);
+ while (i < entries &&
+ !strcmp(ce->name, active_cache[i]->name))
+ i++;
+ i--; /* compensate for loop control increments */
+ continue;
+ }
+
+ if (lstat(ce->name, &st) < 0) {
+ if (errno != ENOENT && errno != ENOTDIR) {
+ perror(ce->name);
+ continue;
+ }
+ if (silent)
+ continue;
+ show_file('-', ce);
+ continue;
+ }
+ changed = ce_match_stat(ce, &st);
+ if (!changed && !diff_options.find_copies_harder)
+ continue;
+ oldmode = ntohl(ce->ce_mode);
+
+ newmode = DIFF_FILE_CANON_MODE(st.st_mode);
+ if (!trust_executable_bit &&
+ S_ISREG(newmode) && S_ISREG(oldmode) &&
+ ((newmode ^ oldmode) == 0111))
+ newmode = oldmode;
+ show_modified(oldmode, newmode,
+ ce->sha1, (changed ? null_sha1 : ce->sha1),
+ ce->name);
+ }
+ diffcore_std(&diff_options);
+ diff_flush(&diff_options);
+ return 0;
+}
diff --git a/diff-index.c b/diff-index.c
new file mode 100644
index 000000000..c9a9f4c74
--- /dev/null
+++ b/diff-index.c
@@ -0,0 +1,247 @@
+#include "cache.h"
+#include "diff.h"
+
+static int cached_only = 0;
+static int match_nonexisting = 0;
+static struct diff_options diff_options;
+
+/* A file entry went away or appeared */
+static void show_file(const char *prefix,
+ struct cache_entry *ce,
+ unsigned char *sha1, unsigned int mode)
+{
+ diff_addremove(&diff_options, prefix[0], ntohl(mode),
+ sha1, ce->name, NULL);
+}
+
+static int get_stat_data(struct cache_entry *ce,
+ unsigned char ** sha1p, unsigned int *modep)
+{
+ unsigned char *sha1 = ce->sha1;
+ unsigned int mode = ce->ce_mode;
+
+ if (!cached_only) {
+ static unsigned char no_sha1[20];
+ int changed;
+ struct stat st;
+ if (lstat(ce->name, &st) < 0) {
+ if (errno == ENOENT && match_nonexisting) {
+ *sha1p = sha1;
+ *modep = mode;
+ return 0;
+ }
+ return -1;
+ }
+ changed = ce_match_stat(ce, &st);
+ if (changed) {
+ mode = create_ce_mode(st.st_mode);
+ if (!trust_executable_bit &&
+ S_ISREG(mode) && S_ISREG(ce->ce_mode) &&
+ ((mode ^ ce->ce_mode) == 0111))
+ mode = ce->ce_mode;
+ sha1 = no_sha1;
+ }
+ }
+
+ *sha1p = sha1;
+ *modep = mode;
+ return 0;
+}
+
+static void show_new_file(struct cache_entry *new)
+{
+ unsigned char *sha1;
+ unsigned int mode;
+
+ /* New file in the index: it might actually be different in
+ * the working copy.
+ */
+ if (get_stat_data(new, &sha1, &mode) < 0)
+ return;
+
+ show_file("+", new, sha1, mode);
+}
+
+static int show_modified(struct cache_entry *old,
+ struct cache_entry *new,
+ int report_missing)
+{
+ unsigned int mode, oldmode;
+ unsigned char *sha1;
+
+ if (get_stat_data(new, &sha1, &mode) < 0) {
+ if (report_missing)
+ show_file("-", old, old->sha1, old->ce_mode);
+ return -1;
+ }
+
+ oldmode = old->ce_mode;
+ if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
+ !diff_options.find_copies_harder)
+ return 0;
+
+ mode = ntohl(mode);
+ oldmode = ntohl(oldmode);
+
+ diff_change(&diff_options, oldmode, mode,
+ old->sha1, sha1, old->name, NULL);
+ return 0;
+}
+
+static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec)
+{
+ while (entries) {
+ struct cache_entry *ce = *ac;
+ int same = (entries > 1) && ce_same_name(ce, ac[1]);
+
+ if (!ce_path_match(ce, pathspec))
+ goto skip_entry;
+
+ switch (ce_stage(ce)) {
+ case 0:
+ /* No stage 1 entry? That means it's a new file */
+ if (!same) {
+ show_new_file(ce);
+ break;
+ }
+ /* Show difference between old and new */
+ show_modified(ac[1], ce, 1);
+ break;
+ case 1:
+ /* No stage 3 (merge) entry? That means it's been deleted */
+ if (!same) {
+ show_file("-", ce, ce->sha1, ce->ce_mode);
+ break;
+ }
+ /* We come here with ce pointing at stage 1
+ * (original tree) and ac[1] pointing at stage
+ * 3 (unmerged). show-modified with
+ * report-mising set to false does not say the
+ * file is deleted but reports true if work
+ * tree does not have it, in which case we
+ * fall through to report the unmerged state.
+ * Otherwise, we show the differences between
+ * the original tree and the work tree.
+ */
+ if (!cached_only && !show_modified(ce, ac[1], 0))
+ break;
+ /* fallthru */
+ case 3:
+ diff_unmerge(&diff_options, ce->name);
+ break;
+
+ default:
+ die("impossible cache entry stage");
+ }
+
+skip_entry:
+ /*
+ * Ignore all the different stages for this file,
+ * we've handled the relevant cases now.
+ */
+ do {
+ ac++;
+ entries--;
+ } while (entries && ce_same_name(ce, ac[0]));
+ }
+ return 0;
+}
+
+/*
+ * This turns all merge entries into "stage 3". That guarantees that
+ * when we read in the new tree (into "stage 1"), we won't lose sight
+ * of the fact that we had unmerged entries.
+ */
+static void mark_merge_entries(void)
+{
+ int i;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (!ce_stage(ce))
+ continue;
+ ce->ce_flags |= htons(CE_STAGEMASK);
+ }
+}
+
+static const char diff_cache_usage[] =
+"git-diff-index [-m] [--cached] "
+"[<common diff options>] <tree-ish> [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+int main(int argc, const char **argv)
+{
+ const char *tree_name = NULL;
+ unsigned char sha1[20];
+ const char *prefix = setup_git_directory();
+ const char **pathspec = NULL;
+ void *tree;
+ unsigned long size;
+ int ret;
+ int allow_options = 1;
+ int i;
+
+ git_config(git_default_config);
+ diff_setup(&diff_options);
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ int diff_opt_cnt;
+
+ if (!allow_options || *arg != '-') {
+ if (tree_name)
+ break;
+ tree_name = arg;
+ continue;
+ }
+
+ if (!strcmp(arg, "--")) {
+ allow_options = 0;
+ continue;
+ }
+ if (!strcmp(arg, "-r")) {
+ /* We accept the -r flag just to look like git-diff-tree */
+ continue;
+ }
+ diff_opt_cnt = diff_opt_parse(&diff_options, argv + i,
+ argc - i);
+ if (diff_opt_cnt < 0)
+ usage(diff_cache_usage);
+ else if (diff_opt_cnt) {
+ i += diff_opt_cnt - 1;
+ continue;
+ }
+
+ if (!strcmp(arg, "-m")) {
+ match_nonexisting = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--cached")) {
+ cached_only = 1;
+ continue;
+ }
+ usage(diff_cache_usage);
+ }
+
+ pathspec = get_pathspec(prefix, argv + i);
+
+ if (diff_setup_done(&diff_options) < 0)
+ usage(diff_cache_usage);
+
+ if (!tree_name || get_sha1(tree_name, sha1))
+ usage(diff_cache_usage);
+
+ read_cache();
+
+ mark_merge_entries();
+
+ tree = read_object_with_reference(sha1, "tree", &size, NULL);
+ if (!tree)
+ die("bad tree object %s", tree_name);
+ if (read_tree(tree, size, 1, pathspec))
+ die("unable to read tree object %s", tree_name);
+
+ ret = diff_cache(active_cache, active_nr, pathspec);
+
+ diffcore_std(&diff_options);
+ diff_flush(&diff_options);
+ return ret;
+}
diff --git a/diff-stages.c b/diff-stages.c
new file mode 100644
index 000000000..85170b21d
--- /dev/null
+++ b/diff-stages.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2005 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "diff.h"
+
+static struct diff_options diff_options;
+
+static const char diff_stages_usage[] =
+"git-diff-stages [<common diff options>] <stage1> <stage2> [<path>...]"
+COMMON_DIFF_OPTIONS_HELP;
+
+static void diff_stages(int stage1, int stage2)
+{
+ int i = 0;
+ while (i < active_nr) {
+ struct cache_entry *ce, *stages[4] = { NULL, };
+ struct cache_entry *one, *two;
+ const char *name;
+ int len;
+ ce = active_cache[i];
+ len = ce_namelen(ce);
+ name = ce->name;
+ for (;;) {
+ int stage = ce_stage(ce);
+ stages[stage] = ce;
+ if (active_nr <= ++i)
+ break;
+ ce = active_cache[i];
+ if (ce_namelen(ce) != len ||
+ memcmp(name, ce->name, len))
+ break;
+ }
+ one = stages[stage1];
+ two = stages[stage2];
+ if (!one && !two)
+ continue;
+ if (!one)
+ diff_addremove(&diff_options, '+', ntohl(two->ce_mode),
+ two->sha1, name, NULL);
+ else if (!two)
+ diff_addremove(&diff_options, '-', ntohl(one->ce_mode),
+ one->sha1, name, NULL);
+ else if (memcmp(one->sha1, two->sha1, 20) ||
+ (one->ce_mode != two->ce_mode) ||
+ diff_options.find_copies_harder)
+ diff_change(&diff_options,
+ ntohl(one->ce_mode), ntohl(two->ce_mode),
+ one->sha1, two->sha1, name, NULL);
+ }
+}
+
+int main(int ac, const char **av)
+{
+ int stage1, stage2;
+
+ read_cache();
+ diff_setup(&diff_options);
+ while (1 < ac && av[1][0] == '-') {
+ const char *arg = av[1];
+ if (!strcmp(arg, "-r"))
+ ; /* as usual */
+ else {
+ int diff_opt_cnt;
+ diff_opt_cnt = diff_opt_parse(&diff_options,
+ av+1, ac-1);
+ if (diff_opt_cnt < 0)
+ usage(diff_stages_usage);
+ else if (diff_opt_cnt) {
+ av += diff_opt_cnt;
+ ac -= diff_opt_cnt;
+ continue;
+ }
+ else
+ usage(diff_stages_usage);
+ }
+ ac--; av++;
+ }
+
+ if (ac < 3 ||
+ sscanf(av[1], "%d", &stage1) != 1 ||
+ ! (0 <= stage1 && stage1 <= 3) ||
+ sscanf(av[2], "%d", &stage2) != 1 ||
+ ! (0 <= stage2 && stage2 <= 3))
+ usage(diff_stages_usage);
+
+ av += 3; /* The rest from av[0] are for paths restriction. */
+ diff_options.paths = av;
+
+ if (diff_setup_done(&diff_options) < 0)
+ usage(diff_stages_usage);
+
+ diff_stages(stage1, stage2);
+ diffcore_std(&diff_options);
+ diff_flush(&diff_options);
+ return 0;
+}
diff --git a/diff-tree.c b/diff-tree.c
new file mode 100644
index 000000000..09d16ad66
--- /dev/null
+++ b/diff-tree.c
@@ -0,0 +1,270 @@
+#include "cache.h"
+#include "diff.h"
+#include "commit.h"
+
+static int show_root_diff = 0;
+static int no_commit_id = 0;
+static int verbose_header = 0;
+static int ignore_merges = 1;
+static int read_stdin = 0;
+
+static const char *header = NULL;
+static const char *header_prefix = "";
+static enum cmit_fmt commit_format = CMIT_FMT_RAW;
+
+static struct diff_options diff_options;
+
+static void call_diff_setup_done(void)
+{
+ diff_setup_done(&diff_options);
+}
+
+static int call_diff_flush(void)
+{
+ diffcore_std(&diff_options);
+ if (diff_queue_is_empty()) {
+ int saved_fmt = diff_options.output_format;
+ diff_options.output_format = DIFF_FORMAT_NO_OUTPUT;
+ diff_flush(&diff_options);
+ diff_options.output_format = saved_fmt;
+ return 0;
+ }
+ if (header) {
+ if (!no_commit_id)
+ printf("%s%c", header, diff_options.line_termination);
+ header = NULL;
+ }
+ diff_flush(&diff_options);
+ return 1;
+}
+
+static int diff_tree_sha1_top(const unsigned char *old,
+ const unsigned char *new, const char *base)
+{
+ int ret;
+
+ call_diff_setup_done();
+ ret = diff_tree_sha1(old, new, base, &diff_options);
+ call_diff_flush();
+ return ret;
+}
+
+static int diff_root_tree(const unsigned char *new, const char *base)
+{
+ int retval;
+ void *tree;
+ struct tree_desc empty, real;
+
+ call_diff_setup_done();
+ tree = read_object_with_reference(new, "tree", &real.size, NULL);
+ if (!tree)
+ die("unable to read root tree (%s)", sha1_to_hex(new));
+ real.buf = tree;
+
+ empty.buf = "";
+ empty.size = 0;
+ retval = diff_tree(&empty, &real, base, &diff_options);
+ free(tree);
+ call_diff_flush();
+ return retval;
+}
+
+static const char *generate_header(const char *commit, const char *parent, const char *msg, unsigned long len)
+{
+ static char this_header[16384];
+ int offset;
+
+ if (!verbose_header)
+ return commit;
+
+ offset = sprintf(this_header, "%s%s (from %s)\n", header_prefix, commit, parent);
+ offset += pretty_print_commit(commit_format, msg, len, this_header + offset, sizeof(this_header) - offset);
+ return this_header;
+}
+
+static int diff_tree_commit(const unsigned char *commit, const char *name)
+{
+ unsigned long size, offset;
+ char *buf = read_object_with_reference(commit, "commit", &size, NULL);
+
+ if (!buf)
+ return -1;
+
+ if (!name) {
+ static char commit_name[60];
+ strcpy(commit_name, sha1_to_hex(commit));
+ name = commit_name;
+ }
+
+ /* Root commit? */
+ if (show_root_diff && memcmp(buf + 46, "parent ", 7)) {
+ header = generate_header(name, "root", buf, size);
+ diff_root_tree(commit, "");
+ }
+
+ /* More than one parent? */
+ if (ignore_merges) {
+ if (!memcmp(buf + 46 + 48, "parent ", 7))
+ return 0;
+ }
+
+ offset = 46;
+ while (offset + 48 < size && !memcmp(buf + offset, "parent ", 7)) {
+ unsigned char parent[20];
+ if (get_sha1_hex(buf + offset + 7, parent))
+ return -1;
+ header = generate_header(name, sha1_to_hex(parent), buf, size);
+ diff_tree_sha1_top(parent, commit, "");
+ if (!header && verbose_header) {
+ header_prefix = "\ndiff-tree ";
+ /*
+ * Don't print multiple merge entries if we
+ * don't print the diffs.
+ */
+ }
+ offset += 48;
+ }
+ free(buf);
+ return 0;
+}
+
+static int diff_tree_stdin(char *line)
+{
+ int len = strlen(line);
+ unsigned char commit[20], parent[20];
+ static char this_header[1000];
+
+ if (!len || line[len-1] != '\n')
+ return -1;
+ line[len-1] = 0;
+ if (get_sha1_hex(line, commit))
+ return -1;
+ if (isspace(line[40]) && !get_sha1_hex(line+41, parent)) {
+ line[40] = 0;
+ line[81] = 0;
+ sprintf(this_header, "%s (from %s)\n", line, line+41);
+ header = this_header;
+ return diff_tree_sha1_top(parent, commit, "");
+ }
+ line[40] = 0;
+ return diff_tree_commit(commit, line);
+}
+
+static const char diff_tree_usage[] =
+"git-diff-tree [--stdin] [-m] [-s] [-v] [--pretty] [-t] [-r] [--root] "
+"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n"
+" -r diff recursively\n"
+" --root include the initial commit as diff against /dev/null\n"
+COMMON_DIFF_OPTIONS_HELP;
+
+int main(int argc, const char **argv)
+{
+ int nr_sha1;
+ char line[1000];
+ unsigned char sha1[2][20];
+ const char *prefix = setup_git_directory();
+
+ git_config(git_default_config);
+ nr_sha1 = 0;
+ diff_setup(&diff_options);
+
+ for (;;) {
+ int diff_opt_cnt;
+ const char *arg;
+
+ argv++;
+ argc--;
+ arg = *argv;
+ if (!arg)
+ break;
+
+ if (*arg != '-') {
+ if (nr_sha1 < 2 && !get_sha1(arg, sha1[nr_sha1])) {
+ nr_sha1++;
+ continue;
+ }
+ break;
+ }
+
+ diff_opt_cnt = diff_opt_parse(&diff_options, argv, argc);
+ if (diff_opt_cnt < 0)
+ usage(diff_tree_usage);
+ else if (diff_opt_cnt) {
+ argv += diff_opt_cnt - 1;
+ argc -= diff_opt_cnt - 1;
+ continue;
+ }
+
+
+ if (!strcmp(arg, "--")) {
+ argv++;
+ argc--;
+ break;
+ }
+ if (!strcmp(arg, "-r")) {
+ diff_options.recursive = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-t")) {
+ diff_options.recursive = 1;
+ diff_options.tree_in_recursive = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-m")) {
+ ignore_merges = 0;
+ continue;
+ }
+ if (!strcmp(arg, "-v")) {
+ verbose_header = 1;
+ header_prefix = "diff-tree ";
+ continue;
+ }
+ if (!strncmp(arg, "--pretty", 8)) {
+ verbose_header = 1;
+ header_prefix = "diff-tree ";
+ commit_format = get_commit_format(arg+8);
+ continue;
+ }
+ if (!strcmp(arg, "--stdin")) {
+ read_stdin = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--root")) {
+ show_root_diff = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--no-commit-id")) {
+ no_commit_id = 1;
+ continue;
+ }
+ usage(diff_tree_usage);
+ }
+ if (diff_options.output_format == DIFF_FORMAT_PATCH)
+ diff_options.recursive = 1;
+
+ diff_tree_setup_paths(get_pathspec(prefix, argv));
+
+ switch (nr_sha1) {
+ case 0:
+ if (!read_stdin)
+ usage(diff_tree_usage);
+ break;
+ case 1:
+ diff_tree_commit(sha1[0], NULL);
+ break;
+ case 2:
+ diff_tree_sha1_top(sha1[0], sha1[1], "");
+ break;
+ }
+
+ if (!read_stdin)
+ return 0;
+
+ if (diff_options.detect_rename)
+ diff_options.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
+ DIFF_SETUP_USE_CACHE);
+ while (fgets(line, sizeof(line), stdin))
+ diff_tree_stdin(line);
+
+ return 0;
+}
diff --git a/diff.c b/diff.c
new file mode 100644
index 000000000..0391e8c42
--- /dev/null
+++ b/diff.c
@@ -0,0 +1,1342 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include "cache.h"
+#include "quote.h"
+#include "diff.h"
+#include "diffcore.h"
+
+static const char *diff_opts = "-pu";
+
+static int use_size_cache;
+
+int diff_rename_limit_default = -1;
+
+static char *quote_one(const char *str)
+{
+ int needlen;
+ char *xp;
+
+ if (!str)
+ return NULL;
+ needlen = quote_c_style(str, NULL, NULL, 0);
+ if (!needlen)
+ return strdup(str);
+ xp = xmalloc(needlen + 1);
+ quote_c_style(str, xp, NULL, 0);
+ return xp;
+}
+
+static char *quote_two(const char *one, const char *two)
+{
+ int need_one = quote_c_style(one, NULL, NULL, 1);
+ int need_two = quote_c_style(two, NULL, NULL, 1);
+ char *xp;
+
+ if (need_one + need_two) {
+ if (!need_one) need_one = strlen(one);
+ if (!need_two) need_one = strlen(two);
+
+ xp = xmalloc(need_one + need_two + 3);
+ xp[0] = '"';
+ quote_c_style(one, xp + 1, NULL, 1);
+ quote_c_style(two, xp + need_one + 1, NULL, 1);
+ strcpy(xp + need_one + need_two + 1, "\"");
+ return xp;
+ }
+ need_one = strlen(one);
+ need_two = strlen(two);
+ xp = xmalloc(need_one + need_two + 1);
+ strcpy(xp, one);
+ strcpy(xp + need_one, two);
+ return xp;
+}
+
+static const char *external_diff(void)
+{
+ static const char *external_diff_cmd = NULL;
+ static int done_preparing = 0;
+ const char *env_diff_opts;
+
+ if (done_preparing)
+ return external_diff_cmd;
+
+ /*
+ * Default values above are meant to match the
+ * Linux kernel development style. Examples of
+ * alternative styles you can specify via environment
+ * variables are:
+ *
+ * GIT_DIFF_OPTS="-c";
+ */
+ external_diff_cmd = getenv("GIT_EXTERNAL_DIFF");
+
+ /* In case external diff fails... */
+ env_diff_opts = getenv("GIT_DIFF_OPTS");
+ if (env_diff_opts) diff_opts = env_diff_opts;
+
+ done_preparing = 1;
+ return external_diff_cmd;
+}
+
+#define TEMPFILE_PATH_LEN 50
+
+static struct diff_tempfile {
+ const char *name; /* filename external diff should read from */
+ char hex[41];
+ char mode[10];
+ char tmp_path[TEMPFILE_PATH_LEN];
+} diff_temp[2];
+
+static int count_lines(const char *filename)
+{
+ FILE *in;
+ int count, ch, completely_empty = 1, nl_just_seen = 0;
+ in = fopen(filename, "r");
+ count = 0;
+ while ((ch = fgetc(in)) != EOF)
+ if (ch == '\n') {
+ count++;
+ nl_just_seen = 1;
+ completely_empty = 0;
+ }
+ else {
+ nl_just_seen = 0;
+ completely_empty = 0;
+ }
+ fclose(in);
+ if (completely_empty)
+ return 0;
+ if (!nl_just_seen)
+ count++; /* no trailing newline */
+ return count;
+}
+
+static void print_line_count(int count)
+{
+ switch (count) {
+ case 0:
+ printf("0,0");
+ break;
+ case 1:
+ printf("1");
+ break;
+ default:
+ printf("1,%d", count);
+ break;
+ }
+}
+
+static void copy_file(int prefix, const char *filename)
+{
+ FILE *in;
+ int ch, nl_just_seen = 1;
+ in = fopen(filename, "r");
+ while ((ch = fgetc(in)) != EOF) {
+ if (nl_just_seen)
+ putchar(prefix);
+ putchar(ch);
+ if (ch == '\n')
+ nl_just_seen = 1;
+ else
+ nl_just_seen = 0;
+ }
+ fclose(in);
+ if (!nl_just_seen)
+ printf("\n\\ No newline at end of file\n");
+}
+
+static void emit_rewrite_diff(const char *name_a,
+ const char *name_b,
+ struct diff_tempfile *temp)
+{
+ /* Use temp[i].name as input, name_a and name_b as labels */
+ int lc_a, lc_b;
+ lc_a = count_lines(temp[0].name);
+ lc_b = count_lines(temp[1].name);
+ printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+ print_line_count(lc_a);
+ printf(" +");
+ print_line_count(lc_b);
+ printf(" @@\n");
+ if (lc_a)
+ copy_file('-', temp[0].name);
+ if (lc_b)
+ copy_file('+', temp[1].name);
+}
+
+static void builtin_diff(const char *name_a,
+ const char *name_b,
+ struct diff_tempfile *temp,
+ const char *xfrm_msg,
+ int complete_rewrite)
+{
+ int i, next_at, cmd_size;
+ const char *const diff_cmd = "diff -L%s -L%s";
+ const char *const diff_arg = "-- %s %s||:"; /* "||:" is to return 0 */
+ const char *input_name_sq[2];
+ const char *label_path[2];
+ char *cmd;
+
+ /* diff_cmd and diff_arg have 4 %s in total which makes
+ * the sum of these strings 8 bytes larger than required.
+ * we use 2 spaces around diff-opts, and we need to count
+ * terminating NUL; we used to subtract 5 here, but we do not
+ * care about small leaks in this subprocess that is about
+ * to exec "diff" anymore.
+ */
+ cmd_size = (strlen(diff_cmd) + strlen(diff_opts) + strlen(diff_arg)
+ + 128);
+
+ for (i = 0; i < 2; i++) {
+ input_name_sq[i] = sq_quote(temp[i].name);
+ if (!strcmp(temp[i].name, "/dev/null"))
+ label_path[i] = "/dev/null";
+ else if (!i)
+ label_path[i] = sq_quote(quote_two("a/", name_a));
+ else
+ label_path[i] = sq_quote(quote_two("b/", name_b));
+ cmd_size += (strlen(label_path[i]) + strlen(input_name_sq[i]));
+ }
+
+ cmd = xmalloc(cmd_size);
+
+ next_at = 0;
+ next_at += snprintf(cmd+next_at, cmd_size-next_at,
+ diff_cmd, label_path[0], label_path[1]);
+ next_at += snprintf(cmd+next_at, cmd_size-next_at,
+ " %s ", diff_opts);
+ next_at += snprintf(cmd+next_at, cmd_size-next_at,
+ diff_arg, input_name_sq[0], input_name_sq[1]);
+
+ printf("diff --git %s %s\n",
+ quote_two("a/", name_a), quote_two("b/", name_b));
+ if (label_path[0][0] == '/') {
+ /* dev/null */
+ printf("new file mode %s\n", temp[1].mode);
+ if (xfrm_msg && xfrm_msg[0])
+ puts(xfrm_msg);
+ }
+ else if (label_path[1][0] == '/') {
+ printf("deleted file mode %s\n", temp[0].mode);
+ if (xfrm_msg && xfrm_msg[0])
+ puts(xfrm_msg);
+ }
+ else {
+ if (strcmp(temp[0].mode, temp[1].mode)) {
+ printf("old mode %s\n", temp[0].mode);
+ printf("new mode %s\n", temp[1].mode);
+ }
+ if (xfrm_msg && xfrm_msg[0])
+ puts(xfrm_msg);
+ if (strncmp(temp[0].mode, temp[1].mode, 3))
+ /* we do not run diff between different kind
+ * of objects.
+ */
+ exit(0);
+ if (complete_rewrite) {
+ fflush(NULL);
+ emit_rewrite_diff(name_a, name_b, temp);
+ exit(0);
+ }
+ }
+ fflush(NULL);
+ execlp("/bin/sh","sh", "-c", cmd, NULL);
+}
+
+struct diff_filespec *alloc_filespec(const char *path)
+{
+ int namelen = strlen(path);
+ struct diff_filespec *spec = xmalloc(sizeof(*spec) + namelen + 1);
+
+ memset(spec, 0, sizeof(*spec));
+ spec->path = (char *)(spec + 1);
+ memcpy(spec->path, path, namelen+1);
+ return spec;
+}
+
+void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
+ unsigned short mode)
+{
+ if (mode) {
+ spec->mode = DIFF_FILE_CANON_MODE(mode);
+ memcpy(spec->sha1, sha1, 20);
+ spec->sha1_valid = !!memcmp(sha1, null_sha1, 20);
+ }
+}
+
+/*
+ * Given a name and sha1 pair, if the dircache tells us the file in
+ * the work tree has that object contents, return true, so that
+ * prepare_temp_file() does not have to inflate and extract.
+ */
+static int work_tree_matches(const char *name, const unsigned char *sha1)
+{
+ struct cache_entry *ce;
+ struct stat st;
+ int pos, len;
+
+ /* We do not read the cache ourselves here, because the
+ * benchmark with my previous version that always reads cache
+ * shows that it makes things worse for diff-tree comparing
+ * two linux-2.6 kernel trees in an already checked out work
+ * tree. This is because most diff-tree comparisons deal with
+ * only a small number of files, while reading the cache is
+ * expensive for a large project, and its cost outweighs the
+ * savings we get by not inflating the object to a temporary
+ * file. Practically, this code only helps when we are used
+ * by diff-cache --cached, which does read the cache before
+ * calling us.
+ */
+ if (!active_cache)
+ return 0;
+
+ len = strlen(name);
+ pos = cache_name_pos(name, len);
+ if (pos < 0)
+ return 0;
+ ce = active_cache[pos];
+ if ((lstat(name, &st) < 0) ||
+ !S_ISREG(st.st_mode) || /* careful! */
+ ce_match_stat(ce, &st) ||
+ memcmp(sha1, ce->sha1, 20))
+ return 0;
+ /* we return 1 only when we can stat, it is a regular file,
+ * stat information matches, and sha1 recorded in the cache
+ * matches. I.e. we know the file in the work tree really is
+ * the same as the <name, sha1> pair.
+ */
+ return 1;
+}
+
+static struct sha1_size_cache {
+ unsigned char sha1[20];
+ unsigned long size;
+} **sha1_size_cache;
+static int sha1_size_cache_nr, sha1_size_cache_alloc;
+
+static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
+ int find_only,
+ unsigned long size)
+{
+ int first, last;
+ struct sha1_size_cache *e;
+
+ first = 0;
+ last = sha1_size_cache_nr;
+ while (last > first) {
+ int cmp, next = (last + first) >> 1;
+ e = sha1_size_cache[next];
+ cmp = memcmp(e->sha1, sha1, 20);
+ if (!cmp)
+ return e;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ /* not found */
+ if (find_only)
+ return NULL;
+ /* insert to make it at "first" */
+ if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
+ sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
+ sha1_size_cache = xrealloc(sha1_size_cache,
+ sha1_size_cache_alloc *
+ sizeof(*sha1_size_cache));
+ }
+ sha1_size_cache_nr++;
+ if (first < sha1_size_cache_nr)
+ memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
+ (sha1_size_cache_nr - first - 1) *
+ sizeof(*sha1_size_cache));
+ e = xmalloc(sizeof(struct sha1_size_cache));
+ sha1_size_cache[first] = e;
+ memcpy(e->sha1, sha1, 20);
+ e->size = size;
+ return e;
+}
+
+/*
+ * While doing rename detection and pickaxe operation, we may need to
+ * grab the data for the blob (or file) for our own in-core comparison.
+ * diff_filespec has data and size fields for this purpose.
+ */
+int diff_populate_filespec(struct diff_filespec *s, int size_only)
+{
+ int err = 0;
+ if (!DIFF_FILE_VALID(s))
+ die("internal error: asking to populate invalid file.");
+ if (S_ISDIR(s->mode))
+ return -1;
+
+ if (!use_size_cache)
+ size_only = 0;
+
+ if (s->data)
+ return err;
+ if (!s->sha1_valid ||
+ work_tree_matches(s->path, s->sha1)) {
+ struct stat st;
+ int fd;
+ if (lstat(s->path, &st) < 0) {
+ if (errno == ENOENT) {
+ err_empty:
+ err = -1;
+ empty:
+ s->data = "";
+ s->size = 0;
+ return err;
+ }
+ }
+ s->size = st.st_size;
+ if (!s->size)
+ goto empty;
+ if (size_only)
+ return 0;
+ if (S_ISLNK(st.st_mode)) {
+ int ret;
+ s->data = xmalloc(s->size);
+ s->should_free = 1;
+ ret = readlink(s->path, s->data, s->size);
+ if (ret < 0) {
+ free(s->data);
+ goto err_empty;
+ }
+ return 0;
+ }
+ fd = open(s->path, O_RDONLY);
+ if (fd < 0)
+ goto err_empty;
+ s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (s->data == MAP_FAILED)
+ goto err_empty;
+ s->should_munmap = 1;
+ }
+ else {
+ char type[20];
+ struct sha1_size_cache *e;
+
+ if (size_only) {
+ e = locate_size_cache(s->sha1, 1, 0);
+ if (e) {
+ s->size = e->size;
+ return 0;
+ }
+ if (!sha1_object_info(s->sha1, type, &s->size))
+ locate_size_cache(s->sha1, 0, s->size);
+ }
+ else {
+ s->data = read_sha1_file(s->sha1, type, &s->size);
+ s->should_free = 1;
+ }
+ }
+ return 0;
+}
+
+void diff_free_filespec_data(struct diff_filespec *s)
+{
+ if (s->should_free)
+ free(s->data);
+ else if (s->should_munmap)
+ munmap(s->data, s->size);
+ s->should_free = s->should_munmap = 0;
+ s->data = NULL;
+}
+
+static void prep_temp_blob(struct diff_tempfile *temp,
+ void *blob,
+ unsigned long size,
+ const unsigned char *sha1,
+ int mode)
+{
+ int fd;
+
+ fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
+ if (fd < 0)
+ die("unable to create temp-file");
+ if (write(fd, blob, size) != size)
+ die("unable to write temp-file");
+ close(fd);
+ temp->name = temp->tmp_path;
+ strcpy(temp->hex, sha1_to_hex(sha1));
+ temp->hex[40] = 0;
+ sprintf(temp->mode, "%06o", mode);
+}
+
+static void prepare_temp_file(const char *name,
+ struct diff_tempfile *temp,
+ struct diff_filespec *one)
+{
+ if (!DIFF_FILE_VALID(one)) {
+ not_a_valid_file:
+ /* A '-' entry produces this for file-2, and
+ * a '+' entry produces this for file-1.
+ */
+ temp->name = "/dev/null";
+ strcpy(temp->hex, ".");
+ strcpy(temp->mode, ".");
+ return;
+ }
+
+ if (!one->sha1_valid ||
+ work_tree_matches(name, one->sha1)) {
+ struct stat st;
+ if (lstat(name, &st) < 0) {
+ if (errno == ENOENT)
+ goto not_a_valid_file;
+ die("stat(%s): %s", name, strerror(errno));
+ }
+ if (S_ISLNK(st.st_mode)) {
+ int ret;
+ char *buf, buf_[1024];
+ buf = ((sizeof(buf_) < st.st_size) ?
+ xmalloc(st.st_size) : buf_);
+ ret = readlink(name, buf, st.st_size);
+ if (ret < 0)
+ die("readlink(%s)", name);
+ prep_temp_blob(temp, buf, st.st_size,
+ (one->sha1_valid ?
+ one->sha1 : null_sha1),
+ (one->sha1_valid ?
+ one->mode : S_IFLNK));
+ }
+ else {
+ /* we can borrow from the file in the work tree */
+ temp->name = name;
+ if (!one->sha1_valid)
+ strcpy(temp->hex, sha1_to_hex(null_sha1));
+ else
+ strcpy(temp->hex, sha1_to_hex(one->sha1));
+ /* Even though we may sometimes borrow the
+ * contents from the work tree, we always want
+ * one->mode. mode is trustworthy even when
+ * !(one->sha1_valid), as long as
+ * DIFF_FILE_VALID(one).
+ */
+ sprintf(temp->mode, "%06o", one->mode);
+ }
+ return;
+ }
+ else {
+ if (diff_populate_filespec(one, 0))
+ die("cannot read data blob for %s", one->path);
+ prep_temp_blob(temp, one->data, one->size,
+ one->sha1, one->mode);
+ }
+}
+
+static void remove_tempfile(void)
+{
+ int i;
+
+ for (i = 0; i < 2; i++)
+ if (diff_temp[i].name == diff_temp[i].tmp_path) {
+ unlink(diff_temp[i].name);
+ diff_temp[i].name = NULL;
+ }
+}
+
+static void remove_tempfile_on_signal(int signo)
+{
+ remove_tempfile();
+}
+
+/* An external diff command takes:
+ *
+ * diff-cmd name infile1 infile1-sha1 infile1-mode \
+ * infile2 infile2-sha1 infile2-mode [ rename-to ]
+ *
+ */
+static void run_external_diff(const char *pgm,
+ const char *name,
+ const char *other,
+ struct diff_filespec *one,
+ struct diff_filespec *two,
+ const char *xfrm_msg,
+ int complete_rewrite)
+{
+ struct diff_tempfile *temp = diff_temp;
+ pid_t pid;
+ int status;
+ static int atexit_asked = 0;
+ const char *othername;
+
+ othername = (other? other : name);
+ if (one && two) {
+ prepare_temp_file(name, &temp[0], one);
+ prepare_temp_file(othername, &temp[1], two);
+ if (! atexit_asked &&
+ (temp[0].name == temp[0].tmp_path ||
+ temp[1].name == temp[1].tmp_path)) {
+ atexit_asked = 1;
+ atexit(remove_tempfile);
+ }
+ signal(SIGINT, remove_tempfile_on_signal);
+ }
+
+ fflush(NULL);
+ pid = fork();
+ if (pid < 0)
+ die("unable to fork");
+ if (!pid) {
+ if (pgm) {
+ if (one && two) {
+ const char *exec_arg[10];
+ const char **arg = &exec_arg[0];
+ *arg++ = pgm;
+ *arg++ = name;
+ *arg++ = temp[0].name;
+ *arg++ = temp[0].hex;
+ *arg++ = temp[0].mode;
+ *arg++ = temp[1].name;
+ *arg++ = temp[1].hex;
+ *arg++ = temp[1].mode;
+ if (other) {
+ *arg++ = other;
+ *arg++ = xfrm_msg;
+ }
+ *arg = NULL;
+ execvp(pgm, (char *const*) exec_arg);
+ }
+ else
+ execlp(pgm, pgm, name, NULL);
+ }
+ /*
+ * otherwise we use the built-in one.
+ */
+ if (one && two)
+ builtin_diff(name, othername, temp, xfrm_msg,
+ complete_rewrite);
+ else
+ printf("* Unmerged path %s\n", name);
+ exit(0);
+ }
+ if (waitpid(pid, &status, 0) < 0 ||
+ !WIFEXITED(status) || WEXITSTATUS(status)) {
+ /* Earlier we did not check the exit status because
+ * diff exits non-zero if files are different, and
+ * we are not interested in knowing that. It was a
+ * mistake which made it harder to quit a diff-*
+ * session that uses the git-apply-patch-script as
+ * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF
+ * should also exit non-zero only when it wants to
+ * abort the entire diff-* session.
+ */
+ remove_tempfile();
+ fprintf(stderr, "external diff died, stopping at %s.\n", name);
+ exit(1);
+ }
+ remove_tempfile();
+}
+
+static void diff_fill_sha1_info(struct diff_filespec *one)
+{
+ if (DIFF_FILE_VALID(one)) {
+ if (!one->sha1_valid) {
+ struct stat st;
+ if (stat(one->path, &st) < 0)
+ die("stat %s", one->path);
+ if (index_path(one->sha1, one->path, &st, 0))
+ die("cannot hash %s\n", one->path);
+ }
+ }
+ else
+ memset(one->sha1, 0, 20);
+}
+
+static void run_diff(struct diff_filepair *p, struct diff_options *o)
+{
+ const char *pgm = external_diff();
+ char msg[PATH_MAX*2+300], *xfrm_msg;
+ struct diff_filespec *one;
+ struct diff_filespec *two;
+ const char *name;
+ const char *other;
+ char *name_munged, *other_munged;
+ int complete_rewrite = 0;
+ int len;
+
+ if (DIFF_PAIR_UNMERGED(p)) {
+ /* unmerged */
+ run_external_diff(pgm, p->one->path, NULL, NULL, NULL, NULL,
+ 0);
+ return;
+ }
+
+ name = p->one->path;
+ other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+ name_munged = quote_one(name);
+ other_munged = quote_one(other);
+ one = p->one; two = p->two;
+
+ diff_fill_sha1_info(one);
+ diff_fill_sha1_info(two);
+
+ len = 0;
+ switch (p->status) {
+ case DIFF_STATUS_COPIED:
+ len += snprintf(msg + len, sizeof(msg) - len,
+ "similarity index %d%%\n"
+ "copy from %s\n"
+ "copy to %s\n",
+ (int)(0.5 + p->score * 100.0/MAX_SCORE),
+ name_munged, other_munged);
+ break;
+ case DIFF_STATUS_RENAMED:
+ len += snprintf(msg + len, sizeof(msg) - len,
+ "similarity index %d%%\n"
+ "rename from %s\n"
+ "rename to %s\n",
+ (int)(0.5 + p->score * 100.0/MAX_SCORE),
+ name_munged, other_munged);
+ break;
+ case DIFF_STATUS_MODIFIED:
+ if (p->score) {
+ len += snprintf(msg + len, sizeof(msg) - len,
+ "dissimilarity index %d%%\n",
+ (int)(0.5 + p->score *
+ 100.0/MAX_SCORE));
+ complete_rewrite = 1;
+ break;
+ }
+ /* fallthru */
+ default:
+ /* nothing */
+ ;
+ }
+
+ if (memcmp(one->sha1, two->sha1, 20)) {
+ char one_sha1[41];
+ const char *index_fmt = o->full_index ? "index %s..%s" : "index %.7s..%.7s";
+ memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
+
+ len += snprintf(msg + len, sizeof(msg) - len,
+ index_fmt, one_sha1, sha1_to_hex(two->sha1));
+ if (one->mode == two->mode)
+ len += snprintf(msg + len, sizeof(msg) - len,
+ " %06o", one->mode);
+ len += snprintf(msg + len, sizeof(msg) - len, "\n");
+ }
+
+ if (len)
+ msg[--len] = 0;
+ xfrm_msg = len ? msg : NULL;
+
+ if (!pgm &&
+ DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) &&
+ (S_IFMT & one->mode) != (S_IFMT & two->mode)) {
+ /* a filepair that changes between file and symlink
+ * needs to be split into deletion and creation.
+ */
+ struct diff_filespec *null = alloc_filespec(two->path);
+ run_external_diff(NULL, name, other, one, null, xfrm_msg, 0);
+ free(null);
+ null = alloc_filespec(one->path);
+ run_external_diff(NULL, name, other, null, two, xfrm_msg, 0);
+ free(null);
+ }
+ else
+ run_external_diff(pgm, name, other, one, two, xfrm_msg,
+ complete_rewrite);
+
+ free(name_munged);
+ free(other_munged);
+}
+
+void diff_setup(struct diff_options *options)
+{
+ memset(options, 0, sizeof(*options));
+ options->output_format = DIFF_FORMAT_RAW;
+ options->line_termination = '\n';
+ options->break_opt = -1;
+ options->rename_limit = -1;
+
+ options->change = diff_change;
+ options->add_remove = diff_addremove;
+}
+
+int diff_setup_done(struct diff_options *options)
+{
+ if ((options->find_copies_harder &&
+ options->detect_rename != DIFF_DETECT_COPY) ||
+ (0 <= options->rename_limit && !options->detect_rename))
+ return -1;
+ if (options->detect_rename && options->rename_limit < 0)
+ options->rename_limit = diff_rename_limit_default;
+ if (options->setup & DIFF_SETUP_USE_CACHE) {
+ if (!active_cache)
+ /* read-cache does not die even when it fails
+ * so it is safe for us to do this here. Also
+ * it does not smudge active_cache or active_nr
+ * when it fails, so we do not have to worry about
+ * cleaning it up oufselves either.
+ */
+ read_cache();
+ }
+ if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
+ use_size_cache = 1;
+
+ return 0;
+}
+
+int diff_opt_parse(struct diff_options *options, const char **av, int ac)
+{
+ const char *arg = av[0];
+ if (!strcmp(arg, "-p") || !strcmp(arg, "-u"))
+ options->output_format = DIFF_FORMAT_PATCH;
+ else if (!strcmp(arg, "-z"))
+ options->line_termination = 0;
+ else if (!strncmp(arg, "-l", 2))
+ options->rename_limit = strtoul(arg+2, NULL, 10);
+ else if (!strcmp(arg, "--full-index"))
+ options->full_index = 1;
+ else if (!strcmp(arg, "--name-only"))
+ options->output_format = DIFF_FORMAT_NAME;
+ else if (!strcmp(arg, "--name-status"))
+ options->output_format = DIFF_FORMAT_NAME_STATUS;
+ else if (!strcmp(arg, "-R"))
+ options->reverse_diff = 1;
+ else if (!strncmp(arg, "-S", 2))
+ options->pickaxe = arg + 2;
+ else if (!strcmp(arg, "-s"))
+ options->output_format = DIFF_FORMAT_NO_OUTPUT;
+ else if (!strncmp(arg, "-O", 2))
+ options->orderfile = arg + 2;
+ else if (!strncmp(arg, "--diff-filter=", 14))
+ options->filter = arg + 14;
+ else if (!strcmp(arg, "--pickaxe-all"))
+ options->pickaxe_opts = DIFF_PICKAXE_ALL;
+ else if (!strncmp(arg, "-B", 2)) {
+ if ((options->break_opt =
+ diff_scoreopt_parse(arg)) == -1)
+ return -1;
+ }
+ else if (!strncmp(arg, "-M", 2)) {
+ if ((options->rename_score =
+ diff_scoreopt_parse(arg)) == -1)
+ return -1;
+ options->detect_rename = DIFF_DETECT_RENAME;
+ }
+ else if (!strncmp(arg, "-C", 2)) {
+ if ((options->rename_score =
+ diff_scoreopt_parse(arg)) == -1)
+ return -1;
+ options->detect_rename = DIFF_DETECT_COPY;
+ }
+ else if (!strcmp(arg, "--find-copies-harder"))
+ options->find_copies_harder = 1;
+ else
+ return 0;
+ return 1;
+}
+
+static int parse_num(const char **cp_p)
+{
+ int num, scale, ch, cnt;
+ const char *cp = *cp_p;
+
+ cnt = num = 0;
+ scale = 1;
+ while ('0' <= (ch = *cp) && ch <= '9') {
+ if (cnt++ < 5) {
+ /* We simply ignore more than 5 digits precision. */
+ scale *= 10;
+ num = num * 10 + ch - '0';
+ }
+ cp++;
+ }
+ *cp_p = cp;
+
+ /* user says num divided by scale and we say internally that
+ * is MAX_SCORE * num / scale.
+ */
+ return (MAX_SCORE * num / scale);
+}
+
+int diff_scoreopt_parse(const char *opt)
+{
+ int opt1, opt2, cmd;
+
+ if (*opt++ != '-')
+ return -1;
+ cmd = *opt++;
+ if (cmd != 'M' && cmd != 'C' && cmd != 'B')
+ return -1; /* that is not a -M, -C nor -B option */
+
+ opt1 = parse_num(&opt);
+ if (cmd != 'B')
+ opt2 = 0;
+ else {
+ if (*opt == 0)
+ opt2 = 0;
+ else if (*opt != '/')
+ return -1; /* we expect -B80/99 or -B80 */
+ else {
+ opt++;
+ opt2 = parse_num(&opt);
+ }
+ }
+ if (*opt != 0)
+ return -1;
+ return opt1 | (opt2 << 16);
+}
+
+struct diff_queue_struct diff_queued_diff;
+
+void diff_q(struct diff_queue_struct *queue, struct diff_filepair *dp)
+{
+ if (queue->alloc <= queue->nr) {
+ queue->alloc = alloc_nr(queue->alloc);
+ queue->queue = xrealloc(queue->queue,
+ sizeof(dp) * queue->alloc);
+ }
+ queue->queue[queue->nr++] = dp;
+}
+
+struct diff_filepair *diff_queue(struct diff_queue_struct *queue,
+ struct diff_filespec *one,
+ struct diff_filespec *two)
+{
+ struct diff_filepair *dp = xmalloc(sizeof(*dp));
+ dp->one = one;
+ dp->two = two;
+ dp->score = 0;
+ dp->status = 0;
+ dp->source_stays = 0;
+ dp->broken_pair = 0;
+ if (queue)
+ diff_q(queue, dp);
+ return dp;
+}
+
+void diff_free_filepair(struct diff_filepair *p)
+{
+ diff_free_filespec_data(p->one);
+ diff_free_filespec_data(p->two);
+ free(p->one);
+ free(p->two);
+ free(p);
+}
+
+static void diff_flush_raw(struct diff_filepair *p,
+ int line_termination,
+ int inter_name_termination,
+ int output_format)
+{
+ int two_paths;
+ char status[10];
+ const char *path_one, *path_two;
+
+ path_one = p->one->path;
+ path_two = p->two->path;
+ if (line_termination) {
+ path_one = quote_one(path_one);
+ path_two = quote_one(path_two);
+ }
+
+ if (p->score)
+ sprintf(status, "%c%03d", p->status,
+ (int)(0.5 + p->score * 100.0/MAX_SCORE));
+ else {
+ status[0] = p->status;
+ status[1] = 0;
+ }
+ switch (p->status) {
+ case DIFF_STATUS_COPIED:
+ case DIFF_STATUS_RENAMED:
+ two_paths = 1;
+ break;
+ case DIFF_STATUS_ADDED:
+ case DIFF_STATUS_DELETED:
+ two_paths = 0;
+ break;
+ default:
+ two_paths = 0;
+ break;
+ }
+ if (output_format != DIFF_FORMAT_NAME_STATUS) {
+ printf(":%06o %06o %s ",
+ p->one->mode, p->two->mode, sha1_to_hex(p->one->sha1));
+ printf("%s ", sha1_to_hex(p->two->sha1));
+ }
+ printf("%s%c%s", status, inter_name_termination, path_one);
+ if (two_paths)
+ printf("%c%s", inter_name_termination, path_two);
+ putchar(line_termination);
+ if (path_one != p->one->path)
+ free((void*)path_one);
+ if (path_two != p->two->path)
+ free((void*)path_two);
+}
+
+static void diff_flush_name(struct diff_filepair *p,
+ int inter_name_termination,
+ int line_termination)
+{
+ char *path = p->two->path;
+
+ if (line_termination)
+ path = quote_one(p->two->path);
+ else
+ path = p->two->path;
+ printf("%s%c", path, line_termination);
+ if (p->two->path != path)
+ free(path);
+}
+
+int diff_unmodified_pair(struct diff_filepair *p)
+{
+ /* This function is written stricter than necessary to support
+ * the currently implemented transformers, but the idea is to
+ * let transformers to produce diff_filepairs any way they want,
+ * and filter and clean them up here before producing the output.
+ */
+ struct diff_filespec *one, *two;
+
+ if (DIFF_PAIR_UNMERGED(p))
+ return 0; /* unmerged is interesting */
+
+ one = p->one;
+ two = p->two;
+
+ /* deletion, addition, mode or type change
+ * and rename are all interesting.
+ */
+ if (DIFF_FILE_VALID(one) != DIFF_FILE_VALID(two) ||
+ DIFF_PAIR_MODE_CHANGED(p) ||
+ strcmp(one->path, two->path))
+ return 0;
+
+ /* both are valid and point at the same path. that is, we are
+ * dealing with a change.
+ */
+ if (one->sha1_valid && two->sha1_valid &&
+ !memcmp(one->sha1, two->sha1, sizeof(one->sha1)))
+ return 1; /* no change */
+ if (!one->sha1_valid && !two->sha1_valid)
+ return 1; /* both look at the same file on the filesystem. */
+ return 0;
+}
+
+static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
+{
+ if (diff_unmodified_pair(p))
+ return;
+
+ if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+ (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+ return; /* no tree diffs in patch format */
+
+ run_diff(p, o);
+}
+
+int diff_queue_is_empty(void)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i;
+ for (i = 0; i < q->nr; i++)
+ if (!diff_unmodified_pair(q->queue[i]))
+ return 0;
+ return 1;
+}
+
+#if DIFF_DEBUG
+void diff_debug_filespec(struct diff_filespec *s, int x, const char *one)
+{
+ fprintf(stderr, "queue[%d] %s (%s) %s %06o %s\n",
+ x, one ? one : "",
+ s->path,
+ DIFF_FILE_VALID(s) ? "valid" : "invalid",
+ s->mode,
+ s->sha1_valid ? sha1_to_hex(s->sha1) : "");
+ fprintf(stderr, "queue[%d] %s size %lu flags %d\n",
+ x, one ? one : "",
+ s->size, s->xfrm_flags);
+}
+
+void diff_debug_filepair(const struct diff_filepair *p, int i)
+{
+ diff_debug_filespec(p->one, i, "one");
+ diff_debug_filespec(p->two, i, "two");
+ fprintf(stderr, "score %d, status %c stays %d broken %d\n",
+ p->score, p->status ? p->status : '?',
+ p->source_stays, p->broken_pair);
+}
+
+void diff_debug_queue(const char *msg, struct diff_queue_struct *q)
+{
+ int i;
+ if (msg)
+ fprintf(stderr, "%s\n", msg);
+ fprintf(stderr, "q->nr = %d\n", q->nr);
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ diff_debug_filepair(p, i);
+ }
+}
+#endif
+
+static void diff_resolve_rename_copy(void)
+{
+ int i, j;
+ struct diff_filepair *p, *pp;
+ struct diff_queue_struct *q = &diff_queued_diff;
+
+ diff_debug_queue("resolve-rename-copy", q);
+
+ for (i = 0; i < q->nr; i++) {
+ p = q->queue[i];
+ p->status = 0; /* undecided */
+ if (DIFF_PAIR_UNMERGED(p))
+ p->status = DIFF_STATUS_UNMERGED;
+ else if (!DIFF_FILE_VALID(p->one))
+ p->status = DIFF_STATUS_ADDED;
+ else if (!DIFF_FILE_VALID(p->two))
+ p->status = DIFF_STATUS_DELETED;
+ else if (DIFF_PAIR_TYPE_CHANGED(p))
+ p->status = DIFF_STATUS_TYPE_CHANGED;
+
+ /* from this point on, we are dealing with a pair
+ * whose both sides are valid and of the same type, i.e.
+ * either in-place edit or rename/copy edit.
+ */
+ else if (DIFF_PAIR_RENAME(p)) {
+ if (p->source_stays) {
+ p->status = DIFF_STATUS_COPIED;
+ continue;
+ }
+ /* See if there is some other filepair that
+ * copies from the same source as us. If so
+ * we are a copy. Otherwise we are either a
+ * copy if the path stays, or a rename if it
+ * does not, but we already handled "stays" case.
+ */
+ for (j = i + 1; j < q->nr; j++) {
+ pp = q->queue[j];
+ if (strcmp(pp->one->path, p->one->path))
+ continue; /* not us */
+ if (!DIFF_PAIR_RENAME(pp))
+ continue; /* not a rename/copy */
+ /* pp is a rename/copy from the same source */
+ p->status = DIFF_STATUS_COPIED;
+ break;
+ }
+ if (!p->status)
+ p->status = DIFF_STATUS_RENAMED;
+ }
+ else if (memcmp(p->one->sha1, p->two->sha1, 20) ||
+ p->one->mode != p->two->mode)
+ p->status = DIFF_STATUS_MODIFIED;
+ else {
+ /* This is a "no-change" entry and should not
+ * happen anymore, but prepare for broken callers.
+ */
+ error("feeding unmodified %s to diffcore",
+ p->one->path);
+ p->status = DIFF_STATUS_UNKNOWN;
+ }
+ }
+ diff_debug_queue("resolve-rename-copy done", q);
+}
+
+void diff_flush(struct diff_options *options)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i;
+ int inter_name_termination = '\t';
+ int diff_output_format = options->output_format;
+ int line_termination = options->line_termination;
+
+ if (!line_termination)
+ inter_name_termination = 0;
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if ((diff_output_format == DIFF_FORMAT_NO_OUTPUT) ||
+ (p->status == DIFF_STATUS_UNKNOWN))
+ continue;
+ if (p->status == 0)
+ die("internal error in diff-resolve-rename-copy");
+ switch (diff_output_format) {
+ case DIFF_FORMAT_PATCH:
+ diff_flush_patch(p, options);
+ break;
+ case DIFF_FORMAT_RAW:
+ case DIFF_FORMAT_NAME_STATUS:
+ diff_flush_raw(p, line_termination,
+ inter_name_termination,
+ diff_output_format);
+ break;
+ case DIFF_FORMAT_NAME:
+ diff_flush_name(p,
+ inter_name_termination,
+ line_termination);
+ break;
+ }
+ diff_free_filepair(q->queue[i]);
+ }
+ free(q->queue);
+ q->queue = NULL;
+ q->nr = q->alloc = 0;
+}
+
+static void diffcore_apply_filter(const char *filter)
+{
+ int i;
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct diff_queue_struct outq;
+ outq.queue = NULL;
+ outq.nr = outq.alloc = 0;
+
+ if (!filter)
+ return;
+
+ if (strchr(filter, DIFF_STATUS_FILTER_AON)) {
+ int found;
+ for (i = found = 0; !found && i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (((p->status == DIFF_STATUS_MODIFIED) &&
+ ((p->score &&
+ strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+ (!p->score &&
+ strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+ ((p->status != DIFF_STATUS_MODIFIED) &&
+ strchr(filter, p->status)))
+ found++;
+ }
+ if (found)
+ return;
+
+ /* otherwise we will clear the whole queue
+ * by copying the empty outq at the end of this
+ * function, but first clear the current entries
+ * in the queue.
+ */
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+ }
+ else {
+ /* Only the matching ones */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+
+ if (((p->status == DIFF_STATUS_MODIFIED) &&
+ ((p->score &&
+ strchr(filter, DIFF_STATUS_FILTER_BROKEN)) ||
+ (!p->score &&
+ strchr(filter, DIFF_STATUS_MODIFIED)))) ||
+ ((p->status != DIFF_STATUS_MODIFIED) &&
+ strchr(filter, p->status)))
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+ }
+ free(q->queue);
+ *q = outq;
+}
+
+void diffcore_std(struct diff_options *options)
+{
+ if (options->paths && options->paths[0])
+ diffcore_pathspec(options->paths);
+ if (options->break_opt != -1)
+ diffcore_break(options->break_opt);
+ if (options->detect_rename)
+ diffcore_rename(options);
+ if (options->break_opt != -1)
+ diffcore_merge_broken();
+ if (options->pickaxe)
+ diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+ if (options->orderfile)
+ diffcore_order(options->orderfile);
+ diff_resolve_rename_copy();
+ diffcore_apply_filter(options->filter);
+}
+
+
+void diffcore_std_no_resolve(struct diff_options *options)
+{
+ if (options->pickaxe)
+ diffcore_pickaxe(options->pickaxe, options->pickaxe_opts);
+ if (options->orderfile)
+ diffcore_order(options->orderfile);
+ diffcore_apply_filter(options->filter);
+}
+
+void diff_addremove(struct diff_options *options,
+ int addremove, unsigned mode,
+ const unsigned char *sha1,
+ const char *base, const char *path)
+{
+ char concatpath[PATH_MAX];
+ struct diff_filespec *one, *two;
+
+ /* This may look odd, but it is a preparation for
+ * feeding "there are unchanged files which should
+ * not produce diffs, but when you are doing copy
+ * detection you would need them, so here they are"
+ * entries to the diff-core. They will be prefixed
+ * with something like '=' or '*' (I haven't decided
+ * which but should not make any difference).
+ * Feeding the same new and old to diff_change()
+ * also has the same effect.
+ * Before the final output happens, they are pruned after
+ * merged into rename/copy pairs as appropriate.
+ */
+ if (options->reverse_diff)
+ addremove = (addremove == '+' ? '-' :
+ addremove == '-' ? '+' : addremove);
+
+ if (!path) path = "";
+ sprintf(concatpath, "%s%s", base, path);
+ one = alloc_filespec(concatpath);
+ two = alloc_filespec(concatpath);
+
+ if (addremove != '+')
+ fill_filespec(one, sha1, mode);
+ if (addremove != '-')
+ fill_filespec(two, sha1, mode);
+
+ diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_change(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *base, const char *path)
+{
+ char concatpath[PATH_MAX];
+ struct diff_filespec *one, *two;
+
+ if (options->reverse_diff) {
+ unsigned tmp;
+ const unsigned char *tmp_c;
+ tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+ tmp_c = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_c;
+ }
+ if (!path) path = "";
+ sprintf(concatpath, "%s%s", base, path);
+ one = alloc_filespec(concatpath);
+ two = alloc_filespec(concatpath);
+ fill_filespec(one, old_sha1, old_mode);
+ fill_filespec(two, new_sha1, new_mode);
+
+ diff_queue(&diff_queued_diff, one, two);
+}
+
+void diff_unmerge(struct diff_options *options,
+ const char *path)
+{
+ struct diff_filespec *one, *two;
+ one = alloc_filespec(path);
+ two = alloc_filespec(path);
+ diff_queue(&diff_queued_diff, one, two);
+}
diff --git a/diff.h b/diff.h
new file mode 100644
index 000000000..9b2e1e62b
--- /dev/null
+++ b/diff.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#ifndef DIFF_H
+#define DIFF_H
+
+#define DIFF_FILE_CANON_MODE(mode) \
+ (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
+ S_ISLNK(mode) ? S_IFLNK : S_IFDIR)
+
+struct tree_desc {
+ void *buf;
+ unsigned long size;
+};
+
+struct diff_options;
+
+typedef void (*change_fn_t)(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *base, const char *path);
+
+typedef void (*add_remove_fn_t)(struct diff_options *options,
+ int addremove, unsigned mode,
+ const unsigned char *sha1,
+ const char *base, const char *path);
+
+struct diff_options {
+ const char **paths;
+ const char *filter;
+ const char *orderfile;
+ const char *pickaxe;
+ unsigned recursive:1,
+ tree_in_recursive:1,
+ full_index:1;
+ int break_opt;
+ int detect_rename;
+ int find_copies_harder;
+ int line_termination;
+ int output_format;
+ int pickaxe_opts;
+ int rename_score;
+ int reverse_diff;
+ int rename_limit;
+ int setup;
+
+ change_fn_t change;
+ add_remove_fn_t add_remove;
+};
+
+extern void diff_tree_setup_paths(const char **paths);
+extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
+ const char *base, struct diff_options *opt);
+extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
+ const char *base, struct diff_options *opt);
+
+extern void diff_addremove(struct diff_options *,
+ int addremove,
+ unsigned mode,
+ const unsigned char *sha1,
+ const char *base,
+ const char *path);
+
+extern void diff_change(struct diff_options *,
+ unsigned mode1, unsigned mode2,
+ const unsigned char *sha1,
+ const unsigned char *sha2,
+ const char *base, const char *path);
+
+extern void diff_unmerge(struct diff_options *,
+ const char *path);
+
+extern int diff_scoreopt_parse(const char *opt);
+
+#define DIFF_SETUP_REVERSE 1
+#define DIFF_SETUP_USE_CACHE 2
+#define DIFF_SETUP_USE_SIZE_CACHE 4
+
+extern void diff_setup(struct diff_options *);
+extern int diff_opt_parse(struct diff_options *, const char **, int);
+extern int diff_setup_done(struct diff_options *);
+
+#define DIFF_DETECT_RENAME 1
+#define DIFF_DETECT_COPY 2
+
+#define DIFF_PICKAXE_ALL 1
+
+extern void diffcore_std(struct diff_options *);
+
+extern void diffcore_std_no_resolve(struct diff_options *);
+
+#define COMMON_DIFF_OPTIONS_HELP \
+"\ncommon diff options:\n" \
+" -z output diff-raw with lines terminated with NUL.\n" \
+" -p output patch format.\n" \
+" -u synonym for -p.\n" \
+" --name-only show only names of changed files.\n" \
+" --name-status show names and status of changed files.\n" \
+" --full-index show full object name on index ines.\n" \
+" -R swap input file pairs.\n" \
+" -B detect complete rewrites.\n" \
+" -M detect renames.\n" \
+" -C detect copies.\n" \
+" --find-copies-harder\n" \
+" try unchanged files as candidate for copy detection.\n" \
+" -l<n> limit rename attempts up to <n> paths.\n" \
+" -O<file> reorder diffs according to the <file>.\n" \
+" -S<string> find filepair whose only one side contains the string.\n" \
+" --pickaxe-all\n" \
+" show all files diff when -S is used and hit is found.\n"
+
+extern int diff_queue_is_empty(void);
+
+#define DIFF_FORMAT_RAW 1
+#define DIFF_FORMAT_PATCH 2
+#define DIFF_FORMAT_NO_OUTPUT 3
+#define DIFF_FORMAT_NAME 4
+#define DIFF_FORMAT_NAME_STATUS 5
+
+extern void diff_flush(struct diff_options*);
+
+/* diff-raw status letters */
+#define DIFF_STATUS_ADDED 'A'
+#define DIFF_STATUS_COPIED 'C'
+#define DIFF_STATUS_DELETED 'D'
+#define DIFF_STATUS_MODIFIED 'M'
+#define DIFF_STATUS_RENAMED 'R'
+#define DIFF_STATUS_TYPE_CHANGED 'T'
+#define DIFF_STATUS_UNKNOWN 'X'
+#define DIFF_STATUS_UNMERGED 'U'
+
+/* these are not diff-raw status letters proper, but used by
+ * diffcore-filter insn to specify additional restrictions.
+ */
+#define DIFF_STATUS_FILTER_AON '*'
+#define DIFF_STATUS_FILTER_BROKEN 'B'
+
+#endif /* DIFF_H */
diff --git a/diffcore-break.c b/diffcore-break.c
new file mode 100644
index 000000000..06f9a7f0e
--- /dev/null
+++ b/diffcore-break.c
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "delta.h"
+#include "count-delta.h"
+
+static int should_break(struct diff_filespec *src,
+ struct diff_filespec *dst,
+ int break_score,
+ int *merge_score_p)
+{
+ /* dst is recorded as a modification of src. Are they so
+ * different that we are better off recording this as a pair
+ * of delete and create?
+ *
+ * There are two criteria used in this algorithm. For the
+ * purposes of helping later rename/copy, we take both delete
+ * and insert into account and estimate the amount of "edit".
+ * If the edit is very large, we break this pair so that
+ * rename/copy can pick the pieces up to match with other
+ * files.
+ *
+ * On the other hand, we would want to ignore inserts for the
+ * pure "complete rewrite" detection. As long as most of the
+ * existing contents were removed from the file, it is a
+ * complete rewrite, and if sizable chunk from the original
+ * still remains in the result, it is not a rewrite. It does
+ * not matter how much or how little new material is added to
+ * the file.
+ *
+ * The score we leave for such a broken filepair uses the
+ * latter definition so that later clean-up stage can find the
+ * pieces that should not have been broken according to the
+ * latter definition after rename/copy runs, and merge the
+ * broken pair that have a score lower than given criteria
+ * back together. The break operation itself happens
+ * according to the former definition.
+ *
+ * The minimum_edit parameter tells us when to break (the
+ * amount of "edit" required for us to consider breaking the
+ * pair). We leave the amount of deletion in *merge_score_p
+ * when we return.
+ *
+ * The value we return is 1 if we want the pair to be broken,
+ * or 0 if we do not.
+ */
+ void *delta;
+ unsigned long delta_size, base_size, src_copied, literal_added;
+ int to_break = 0;
+
+ *merge_score_p = 0; /* assume no deletion --- "do not break"
+ * is the default.
+ */
+
+ if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
+ return 0; /* leave symlink rename alone */
+
+ if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+ return 0; /* error but caught downstream */
+
+ base_size = ((src->size < dst->size) ? src->size : dst->size);
+
+ delta = diff_delta(src->data, src->size,
+ dst->data, dst->size,
+ &delta_size, 0);
+
+ /* Estimate the edit size by interpreting delta. */
+ if (count_delta(delta, delta_size,
+ &src_copied, &literal_added)) {
+ free(delta);
+ return 0; /* we cannot tell */
+ }
+ free(delta);
+
+ /* Compute merge-score, which is "how much is removed
+ * from the source material". The clean-up stage will
+ * merge the surviving pair together if the score is
+ * less than the minimum, after rename/copy runs.
+ */
+ if (src->size <= src_copied)
+ ; /* all copied, nothing removed */
+ else {
+ delta_size = src->size - src_copied;
+ *merge_score_p = delta_size * MAX_SCORE / src->size;
+ }
+
+ /* Extent of damage, which counts both inserts and
+ * deletes.
+ */
+ if (src->size + literal_added <= src_copied)
+ delta_size = 0; /* avoid wrapping around */
+ else
+ delta_size = (src->size - src_copied) + literal_added;
+
+ /* We break if the edit exceeds the minimum.
+ * i.e. (break_score / MAX_SCORE < delta_size / base_size)
+ */
+ if (break_score * base_size < delta_size * MAX_SCORE)
+ to_break = 1;
+
+ return to_break;
+}
+
+void diffcore_break(int break_score)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct diff_queue_struct outq;
+
+ /* When the filepair has this much edit (insert and delete),
+ * it is first considered to be a rewrite and broken into a
+ * create and delete filepair. This is to help breaking a
+ * file that had too much new stuff added, possibly from
+ * moving contents from another file, so that rename/copy can
+ * match it with the other file.
+ *
+ * int break_score; we reuse incoming parameter for this.
+ */
+
+ /* After a pair is broken according to break_score and
+ * subjected to rename/copy, both of them may survive intact,
+ * due to lack of suitable rename/copy peer. Or, the caller
+ * may be calling us without using rename/copy. When that
+ * happens, we merge the broken pieces back into one
+ * modification together if the pair did not have more than
+ * this much delete. For this computation, we do not take
+ * insert into account at all. If you start from a 100-line
+ * file and delete 97 lines of it, it does not matter if you
+ * add 27 lines to it to make a new 30-line file or if you add
+ * 997 lines to it to make a 1000-line file. Either way what
+ * you did was a rewrite of 97%. On the other hand, if you
+ * delete 3 lines, keeping 97 lines intact, it does not matter
+ * if you add 3 lines to it to make a new 100-line file or if
+ * you add 903 lines to it to make a new 1000-line file.
+ * Either way you did a lot of additions and not a rewrite.
+ * This merge happens to catch the latter case. A merge_score
+ * of 80% would be a good default value (a broken pair that
+ * has score lower than merge_score will be merged back
+ * together).
+ */
+ int merge_score;
+ int i;
+
+ /* See comment on DEFAULT_BREAK_SCORE and
+ * DEFAULT_MERGE_SCORE in diffcore.h
+ */
+ merge_score = (break_score >> 16) & 0xFFFF;
+ break_score = (break_score & 0xFFFF);
+
+ if (!break_score)
+ break_score = DEFAULT_BREAK_SCORE;
+ if (!merge_score)
+ merge_score = DEFAULT_MERGE_SCORE;
+
+ outq.nr = outq.alloc = 0;
+ outq.queue = NULL;
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ int score;
+
+ /* We deal only with in-place edit of non directory.
+ * We do not break anything else.
+ */
+ if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two) &&
+ !S_ISDIR(p->one->mode) && !S_ISDIR(p->two->mode) &&
+ !strcmp(p->one->path, p->two->path)) {
+ if (should_break(p->one, p->two,
+ break_score, &score) &&
+ MINIMUM_BREAK_SIZE <= p->one->size) {
+ /* Split this into delete and create */
+ struct diff_filespec *null_one, *null_two;
+ struct diff_filepair *dp;
+
+ /* Set score to 0 for the pair that
+ * needs to be merged back together
+ * should they survive rename/copy.
+ * Also we do not want to break very
+ * small files.
+ */
+ if (score < merge_score)
+ score = 0;
+
+ /* deletion of one */
+ null_one = alloc_filespec(p->one->path);
+ dp = diff_queue(&outq, p->one, null_one);
+ dp->score = score;
+ dp->broken_pair = 1;
+
+ /* creation of two */
+ null_two = alloc_filespec(p->two->path);
+ dp = diff_queue(&outq, null_two, p->two);
+ dp->score = score;
+ dp->broken_pair = 1;
+
+ free(p); /* not diff_free_filepair(), we are
+ * reusing one and two here.
+ */
+ continue;
+ }
+ }
+ diff_q(&outq, p);
+ }
+ free(q->queue);
+ *q = outq;
+
+ return;
+}
+
+static void merge_broken(struct diff_filepair *p,
+ struct diff_filepair *pp,
+ struct diff_queue_struct *outq)
+{
+ /* p and pp are broken pairs we want to merge */
+ struct diff_filepair *c = p, *d = pp, *dp;
+ if (DIFF_FILE_VALID(p->one)) {
+ /* this must be a delete half */
+ d = p; c = pp;
+ }
+ /* Sanity check */
+ if (!DIFF_FILE_VALID(d->one))
+ die("internal error in merge #1");
+ if (DIFF_FILE_VALID(d->two))
+ die("internal error in merge #2");
+ if (DIFF_FILE_VALID(c->one))
+ die("internal error in merge #3");
+ if (!DIFF_FILE_VALID(c->two))
+ die("internal error in merge #4");
+
+ dp = diff_queue(outq, d->one, c->two);
+ dp->score = p->score;
+ diff_free_filespec_data(d->two);
+ diff_free_filespec_data(c->one);
+ free(d);
+ free(c);
+}
+
+void diffcore_merge_broken(void)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct diff_queue_struct outq;
+ int i, j;
+
+ outq.nr = outq.alloc = 0;
+ outq.queue = NULL;
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (!p)
+ /* we already merged this with its peer */
+ continue;
+ else if (p->broken_pair &&
+ !strcmp(p->one->path, p->two->path)) {
+ /* If the peer also survived rename/copy, then
+ * we merge them back together.
+ */
+ for (j = i + 1; j < q->nr; j++) {
+ struct diff_filepair *pp = q->queue[j];
+ if (pp->broken_pair &&
+ !strcmp(pp->one->path, pp->two->path) &&
+ !strcmp(p->one->path, pp->two->path)) {
+ /* Peer survived. Merge them */
+ merge_broken(p, pp, &outq);
+ q->queue[j] = NULL;
+ break;
+ }
+ }
+ if (q->nr <= j)
+ /* The peer did not survive, so we keep
+ * it in the output.
+ */
+ diff_q(&outq, p);
+ }
+ else
+ diff_q(&outq, p);
+ }
+ free(q->queue);
+ *q = outq;
+
+ return;
+}
diff --git a/diffcore-order.c b/diffcore-order.c
new file mode 100644
index 000000000..b38122361
--- /dev/null
+++ b/diffcore-order.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include <fnmatch.h>
+
+static char **order;
+static int order_cnt;
+
+static void prepare_order(const char *orderfile)
+{
+ int fd, cnt, pass;
+ void *map;
+ char *cp, *endp;
+ struct stat st;
+
+ if (order)
+ return;
+
+ fd = open(orderfile, O_RDONLY);
+ if (fd < 0)
+ return;
+ if (fstat(fd, &st)) {
+ close(fd);
+ return;
+ }
+ map = mmap(NULL, st.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (map == MAP_FAILED)
+ return;
+ endp = map + st.st_size;
+ for (pass = 0; pass < 2; pass++) {
+ cnt = 0;
+ cp = map;
+ while (cp < endp) {
+ char *ep;
+ for (ep = cp; ep < endp && *ep != '\n'; ep++)
+ ;
+ /* cp to ep has one line */
+ if (*cp == '\n' || *cp == '#')
+ ; /* comment */
+ else if (pass == 0)
+ cnt++;
+ else {
+ if (*ep == '\n') {
+ *ep = 0;
+ order[cnt] = cp;
+ }
+ else {
+ order[cnt] = xmalloc(ep-cp+1);
+ memcpy(order[cnt], cp, ep-cp);
+ order[cnt][ep-cp] = 0;
+ }
+ cnt++;
+ }
+ if (ep < endp)
+ ep++;
+ cp = ep;
+ }
+ if (pass == 0) {
+ order_cnt = cnt;
+ order = xmalloc(sizeof(*order) * cnt);
+ }
+ }
+}
+
+struct pair_order {
+ struct diff_filepair *pair;
+ int orig_order;
+ int order;
+};
+
+static int match_order(const char *path)
+{
+ int i;
+ char p[PATH_MAX];
+
+ for (i = 0; i < order_cnt; i++) {
+ strcpy(p, path);
+ while (p[0]) {
+ char *cp;
+ if (!fnmatch(order[i], p, 0))
+ return i;
+ cp = strrchr(p, '/');
+ if (!cp)
+ break;
+ *cp = 0;
+ }
+ }
+ return order_cnt;
+}
+
+static int compare_pair_order(const void *a_, const void *b_)
+{
+ struct pair_order const *a, *b;
+ a = (struct pair_order const *)a_;
+ b = (struct pair_order const *)b_;
+ if (a->order != b->order)
+ return a->order - b->order;
+ return a->orig_order - b->orig_order;
+}
+
+void diffcore_order(const char *orderfile)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct pair_order *o = xmalloc(sizeof(*o) * q->nr);
+ int i;
+
+ prepare_order(orderfile);
+ for (i = 0; i < q->nr; i++) {
+ o[i].pair = q->queue[i];
+ o[i].orig_order = i;
+ o[i].order = match_order(o[i].pair->two->path);
+ }
+ qsort(o, q->nr, sizeof(*o), compare_pair_order);
+ for (i = 0; i < q->nr; i++)
+ q->queue[i] = o[i].pair;
+ free(o);
+ return;
+}
diff --git a/diffcore-pathspec.c b/diffcore-pathspec.c
new file mode 100644
index 000000000..68fe00913
--- /dev/null
+++ b/diffcore-pathspec.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+struct path_spec {
+ const char *spec;
+ int len;
+};
+
+static int matches_pathspec(const char *name, struct path_spec *s, int cnt)
+{
+ int i;
+ int namelen;
+
+ if (cnt == 0)
+ return 1;
+
+ namelen = strlen(name);
+ for (i = 0; i < cnt; i++) {
+ int len = s[i].len;
+ if (namelen < len)
+ continue;
+ if (memcmp(s[i].spec, name, len))
+ continue;
+ if (s[i].spec[len-1] == '/' ||
+ name[len] == 0 ||
+ name[len] == '/')
+ return 1;
+ if (!len)
+ return 1;
+ }
+ return 0;
+}
+
+void diffcore_pathspec(const char **pathspec)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ int i, speccnt;
+ struct diff_queue_struct outq;
+ struct path_spec *spec;
+
+ outq.queue = NULL;
+ outq.nr = outq.alloc = 0;
+
+ for (i = 0; pathspec[i]; i++)
+ ;
+ speccnt = i;
+ spec = xmalloc(sizeof(*spec) * speccnt);
+ for (i = 0; pathspec[i]; i++) {
+ spec[i].spec = pathspec[i];
+ spec[i].len = strlen(pathspec[i]);
+ }
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (matches_pathspec(p->two->path, spec, speccnt))
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+ free(q->queue);
+ *q = outq;
+ return;
+}
diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c
new file mode 100644
index 000000000..50e46ab86
--- /dev/null
+++ b/diffcore-pickaxe.c
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+static unsigned int contains(struct diff_filespec *one,
+ const char *needle, unsigned long len)
+{
+ unsigned int cnt;
+ unsigned long offset, sz;
+ const char *data;
+ if (diff_populate_filespec(one, 0))
+ return 0;
+
+ sz = one->size;
+ data = one->data;
+ cnt = 0;
+
+ /* Yes, I've heard of strstr(), but the thing is *data may
+ * not be NUL terminated. Sue me.
+ */
+ for (offset = 0; offset + len <= sz; offset++) {
+ /* we count non-overlapping occurrences of needle */
+ if (!memcmp(needle, data + offset, len)) {
+ offset += len - 1;
+ cnt++;
+ }
+ }
+ return cnt;
+}
+
+void diffcore_pickaxe(const char *needle, int opts)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ unsigned long len = strlen(needle);
+ int i, has_changes;
+ struct diff_queue_struct outq;
+ outq.queue = NULL;
+ outq.nr = outq.alloc = 0;
+
+ if (opts & DIFF_PICKAXE_ALL) {
+ /* Showing the whole changeset if needle exists */
+ for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (!DIFF_FILE_VALID(p->one)) {
+ if (!DIFF_FILE_VALID(p->two))
+ continue; /* ignore unmerged */
+ /* created */
+ if (contains(p->two, needle, len))
+ has_changes++;
+ }
+ else if (!DIFF_FILE_VALID(p->two)) {
+ if (contains(p->one, needle, len))
+ has_changes++;
+ }
+ else if (!diff_unmodified_pair(p) &&
+ contains(p->one, needle, len) !=
+ contains(p->two, needle, len))
+ has_changes++;
+ }
+ if (has_changes)
+ return; /* not munge the queue */
+
+ /* otherwise we will clear the whole queue
+ * by copying the empty outq at the end of this
+ * function, but first clear the current entries
+ * in the queue.
+ */
+ for (i = 0; i < q->nr; i++)
+ diff_free_filepair(q->queue[i]);
+ }
+ else
+ /* Showing only the filepairs that has the needle */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ has_changes = 0;
+ if (!DIFF_FILE_VALID(p->one)) {
+ if (!DIFF_FILE_VALID(p->two))
+ ; /* ignore unmerged */
+ /* created */
+ else if (contains(p->two, needle, len))
+ has_changes = 1;
+ }
+ else if (!DIFF_FILE_VALID(p->two)) {
+ if (contains(p->one, needle, len))
+ has_changes = 1;
+ }
+ else if (!diff_unmodified_pair(p) &&
+ contains(p->one, needle, len) !=
+ contains(p->two, needle, len))
+ has_changes = 1;
+
+ if (has_changes)
+ diff_q(&outq, p);
+ else
+ diff_free_filepair(p);
+ }
+
+ free(q->queue);
+ *q = outq;
+ return;
+}
diff --git a/diffcore-rename.c b/diffcore-rename.c
new file mode 100644
index 000000000..6a9d95d05
--- /dev/null
+++ b/diffcore-rename.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "delta.h"
+#include "count-delta.h"
+
+/* Table of rename/copy destinations */
+
+static struct diff_rename_dst {
+ struct diff_filespec *two;
+ struct diff_filepair *pair;
+} *rename_dst;
+static int rename_dst_nr, rename_dst_alloc;
+
+static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two,
+ int insert_ok)
+{
+ int first, last;
+
+ first = 0;
+ last = rename_dst_nr;
+ while (last > first) {
+ int next = (last + first) >> 1;
+ struct diff_rename_dst *dst = &(rename_dst[next]);
+ int cmp = strcmp(two->path, dst->two->path);
+ if (!cmp)
+ return dst;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ /* not found */
+ if (!insert_ok)
+ return NULL;
+ /* insert to make it at "first" */
+ if (rename_dst_alloc <= rename_dst_nr) {
+ rename_dst_alloc = alloc_nr(rename_dst_alloc);
+ rename_dst = xrealloc(rename_dst,
+ rename_dst_alloc * sizeof(*rename_dst));
+ }
+ rename_dst_nr++;
+ if (first < rename_dst_nr)
+ memmove(rename_dst + first + 1, rename_dst + first,
+ (rename_dst_nr - first - 1) * sizeof(*rename_dst));
+ rename_dst[first].two = alloc_filespec(two->path);
+ fill_filespec(rename_dst[first].two, two->sha1, two->mode);
+ rename_dst[first].pair = NULL;
+ return &(rename_dst[first]);
+}
+
+/* Table of rename/copy src files */
+static struct diff_rename_src {
+ struct diff_filespec *one;
+ unsigned src_path_left : 1;
+} *rename_src;
+static int rename_src_nr, rename_src_alloc;
+
+static struct diff_rename_src *register_rename_src(struct diff_filespec *one,
+ int src_path_left)
+{
+ int first, last;
+
+ first = 0;
+ last = rename_src_nr;
+ while (last > first) {
+ int next = (last + first) >> 1;
+ struct diff_rename_src *src = &(rename_src[next]);
+ int cmp = strcmp(one->path, src->one->path);
+ if (!cmp)
+ return src;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+
+ /* insert to make it at "first" */
+ if (rename_src_alloc <= rename_src_nr) {
+ rename_src_alloc = alloc_nr(rename_src_alloc);
+ rename_src = xrealloc(rename_src,
+ rename_src_alloc * sizeof(*rename_src));
+ }
+ rename_src_nr++;
+ if (first < rename_src_nr)
+ memmove(rename_src + first + 1, rename_src + first,
+ (rename_src_nr - first - 1) * sizeof(*rename_src));
+ rename_src[first].one = one;
+ rename_src[first].src_path_left = src_path_left;
+ return &(rename_src[first]);
+}
+
+static int is_exact_match(struct diff_filespec *src, struct diff_filespec *dst)
+{
+ if (src->sha1_valid && dst->sha1_valid &&
+ !memcmp(src->sha1, dst->sha1, 20))
+ return 1;
+ if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1))
+ return 0;
+ if (src->size != dst->size)
+ return 0;
+ if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+ return 0;
+ if (src->size == dst->size &&
+ !memcmp(src->data, dst->data, src->size))
+ return 1;
+ return 0;
+}
+
+struct diff_score {
+ int src; /* index in rename_src */
+ int dst; /* index in rename_dst */
+ int score;
+};
+
+static int estimate_similarity(struct diff_filespec *src,
+ struct diff_filespec *dst,
+ int minimum_score)
+{
+ /* src points at a file that existed in the original tree (or
+ * optionally a file in the destination tree) and dst points
+ * at a newly created file. They may be quite similar, in which
+ * case we want to say src is renamed to dst or src is copied into
+ * dst, and then some edit has been applied to dst.
+ *
+ * Compare them and return how similar they are, representing
+ * the score as an integer between 0 and MAX_SCORE.
+ *
+ * When there is an exact match, it is considered a better
+ * match than anything else; the destination does not even
+ * call into this function in that case.
+ */
+ void *delta;
+ unsigned long delta_size, base_size, src_copied, literal_added;
+ unsigned long delta_limit;
+ int score;
+
+ /* We deal only with regular files. Symlink renames are handled
+ * only when they are exact matches --- in other words, no edits
+ * after renaming.
+ */
+ if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
+ return 0;
+
+ delta_size = ((src->size < dst->size) ?
+ (dst->size - src->size) : (src->size - dst->size));
+ base_size = ((src->size < dst->size) ? src->size : dst->size);
+
+ /* We would not consider edits that change the file size so
+ * drastically. delta_size must be smaller than
+ * (MAX_SCORE-minimum_score)/MAX_SCORE * min(src->size, dst->size).
+ *
+ * Note that base_size == 0 case is handled here already
+ * and the final score computation below would not have a
+ * divide-by-zero issue.
+ */
+ if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
+ return 0;
+
+ if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+ return 0; /* error but caught downstream */
+
+ delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
+ delta = diff_delta(src->data, src->size,
+ dst->data, dst->size,
+ &delta_size, delta_limit);
+ if (!delta)
+ /* If delta_limit is exceeded, we have too much differences */
+ return 0;
+
+ /* A delta that has a lot of literal additions would have
+ * big delta_size no matter what else it does.
+ */
+ if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
+ return 0;
+
+ /* Estimate the edit size by interpreting delta. */
+ if (count_delta(delta, delta_size, &src_copied, &literal_added)) {
+ free(delta);
+ return 0;
+ }
+ free(delta);
+
+ /* Extent of damage */
+ if (src->size + literal_added < src_copied)
+ delta_size = 0;
+ else
+ delta_size = (src->size - src_copied) + literal_added;
+
+ /*
+ * Now we will give some score to it. 100% edit gets 0 points
+ * and 0% edit gets MAX_SCORE points.
+ */
+ score = MAX_SCORE - (MAX_SCORE * delta_size / base_size);
+ if (score < 0) return 0;
+ if (MAX_SCORE < score) return MAX_SCORE;
+ return score;
+}
+
+static void record_rename_pair(int dst_index, int src_index, int score)
+{
+ struct diff_filespec *one, *two, *src, *dst;
+ struct diff_filepair *dp;
+
+ if (rename_dst[dst_index].pair)
+ die("internal error: dst already matched.");
+
+ src = rename_src[src_index].one;
+ one = alloc_filespec(src->path);
+ fill_filespec(one, src->sha1, src->mode);
+
+ dst = rename_dst[dst_index].two;
+ two = alloc_filespec(dst->path);
+ fill_filespec(two, dst->sha1, dst->mode);
+
+ dp = diff_queue(NULL, one, two);
+ dp->score = score;
+ dp->source_stays = rename_src[src_index].src_path_left;
+ rename_dst[dst_index].pair = dp;
+}
+
+/*
+ * We sort the rename similarity matrix with the score, in descending
+ * order (the most similar first).
+ */
+static int score_compare(const void *a_, const void *b_)
+{
+ const struct diff_score *a = a_, *b = b_;
+ return b->score - a->score;
+}
+
+static int compute_stays(struct diff_queue_struct *q,
+ struct diff_filespec *one)
+{
+ int i;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (strcmp(one->path, p->two->path))
+ continue;
+ if (DIFF_PAIR_RENAME(p)) {
+ return 0; /* something else is renamed into this */
+ }
+ }
+ return 1;
+}
+
+void diffcore_rename(struct diff_options *options)
+{
+ int detect_rename = options->detect_rename;
+ int minimum_score = options->rename_score;
+ int rename_limit = options->rename_limit;
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct diff_queue_struct outq;
+ struct diff_score *mx;
+ int i, j, rename_count;
+ int num_create, num_src, dst_cnt;
+
+ if (!minimum_score)
+ minimum_score = DEFAULT_RENAME_SCORE;
+ rename_count = 0;
+
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (!DIFF_FILE_VALID(p->one))
+ if (!DIFF_FILE_VALID(p->two))
+ continue; /* unmerged */
+ else
+ locate_rename_dst(p->two, 1);
+ else if (!DIFF_FILE_VALID(p->two)) {
+ /* If the source is a broken "delete", and
+ * they did not really want to get broken,
+ * that means the source actually stays.
+ */
+ int stays = (p->broken_pair && !p->score);
+ register_rename_src(p->one, stays);
+ }
+ else if (detect_rename == DIFF_DETECT_COPY)
+ register_rename_src(p->one, 1);
+ }
+ if (rename_dst_nr == 0 ||
+ (0 < rename_limit && rename_limit < rename_dst_nr))
+ goto cleanup; /* nothing to do */
+
+ /* We really want to cull the candidates list early
+ * with cheap tests in order to avoid doing deltas.
+ */
+ for (i = 0; i < rename_dst_nr; i++) {
+ struct diff_filespec *two = rename_dst[i].two;
+ for (j = 0; j < rename_src_nr; j++) {
+ struct diff_filespec *one = rename_src[j].one;
+ if (!is_exact_match(one, two))
+ continue;
+ record_rename_pair(i, j, MAX_SCORE);
+ rename_count++;
+ break; /* we are done with this entry */
+ }
+ }
+
+ /* Have we run out the created file pool? If so we can avoid
+ * doing the delta matrix altogether.
+ */
+ if (rename_count == rename_dst_nr)
+ goto cleanup;
+
+ num_create = (rename_dst_nr - rename_count);
+ num_src = rename_src_nr;
+ mx = xmalloc(sizeof(*mx) * num_create * num_src);
+ for (dst_cnt = i = 0; i < rename_dst_nr; i++) {
+ int base = dst_cnt * num_src;
+ struct diff_filespec *two = rename_dst[i].two;
+ if (rename_dst[i].pair)
+ continue; /* dealt with exact match already. */
+ for (j = 0; j < rename_src_nr; j++) {
+ struct diff_filespec *one = rename_src[j].one;
+ struct diff_score *m = &mx[base+j];
+ m->src = j;
+ m->dst = i;
+ m->score = estimate_similarity(one, two,
+ minimum_score);
+ }
+ dst_cnt++;
+ }
+ /* cost matrix sorted by most to least similar pair */
+ qsort(mx, num_create * num_src, sizeof(*mx), score_compare);
+ for (i = 0; i < num_create * num_src; i++) {
+ struct diff_rename_dst *dst = &rename_dst[mx[i].dst];
+ if (dst->pair)
+ continue; /* already done, either exact or fuzzy. */
+ if (mx[i].score < minimum_score)
+ break; /* there is no more usable pair. */
+ record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
+ rename_count++;
+ }
+ free(mx);
+
+ cleanup:
+ /* At this point, we have found some renames and copies and they
+ * are recorded in rename_dst. The original list is still in *q.
+ */
+ outq.queue = NULL;
+ outq.nr = outq.alloc = 0;
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ struct diff_filepair *pair_to_free = NULL;
+
+ if (!DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two)) {
+ /*
+ * Creation
+ *
+ * We would output this create record if it has
+ * not been turned into a rename/copy already.
+ */
+ struct diff_rename_dst *dst =
+ locate_rename_dst(p->two, 0);
+ if (dst && dst->pair) {
+ diff_q(&outq, dst->pair);
+ pair_to_free = p;
+ }
+ else
+ /* no matching rename/copy source, so
+ * record this as a creation.
+ */
+ diff_q(&outq, p);
+ }
+ else if (DIFF_FILE_VALID(p->one) && !DIFF_FILE_VALID(p->two)) {
+ /*
+ * Deletion
+ *
+ * We would output this delete record if:
+ *
+ * (1) this is a broken delete and the counterpart
+ * broken create remains in the output; or
+ * (2) this is not a broken delete, and rename_dst
+ * does not have a rename/copy to move p->one->path
+ * out of existence.
+ *
+ * Otherwise, the counterpart broken create
+ * has been turned into a rename-edit; or
+ * delete did not have a matching create to
+ * begin with.
+ */
+ if (DIFF_PAIR_BROKEN(p)) {
+ /* broken delete */
+ struct diff_rename_dst *dst =
+ locate_rename_dst(p->one, 0);
+ if (dst && dst->pair)
+ /* counterpart is now rename/copy */
+ pair_to_free = p;
+ }
+ else {
+ for (j = 0; j < rename_dst_nr; j++) {
+ if (!rename_dst[j].pair)
+ continue;
+ if (strcmp(rename_dst[j].pair->
+ one->path,
+ p->one->path))
+ continue;
+ break;
+ }
+ if (j < rename_dst_nr)
+ /* this path remains */
+ pair_to_free = p;
+ }
+
+ if (pair_to_free)
+ ;
+ else
+ diff_q(&outq, p);
+ }
+ else if (!diff_unmodified_pair(p))
+ /* all the usual ones need to be kept */
+ diff_q(&outq, p);
+ else
+ /* no need to keep unmodified pairs */
+ pair_to_free = p;
+
+ if (pair_to_free)
+ diff_free_filepair(pair_to_free);
+ }
+ diff_debug_queue("done copying original", &outq);
+
+ free(q->queue);
+ *q = outq;
+ diff_debug_queue("done collapsing", q);
+
+ /* We need to see which rename source really stays here;
+ * earlier we only checked if the path is left in the result,
+ * but even if a path remains in the result, if that is coming
+ * from copying something else on top of it, then the original
+ * source is lost and does not stay.
+ */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ if (DIFF_PAIR_RENAME(p) && p->source_stays) {
+ /* If one appears as the target of a rename-copy,
+ * then mark p->source_stays = 0; otherwise
+ * leave it as is.
+ */
+ p->source_stays = compute_stays(q, p->one);
+ }
+ }
+
+ for (i = 0; i < rename_dst_nr; i++) {
+ diff_free_filespec_data(rename_dst[i].two);
+ free(rename_dst[i].two);
+ }
+
+ free(rename_dst);
+ rename_dst = NULL;
+ rename_dst_nr = rename_dst_alloc = 0;
+ free(rename_src);
+ rename_src = NULL;
+ rename_src_nr = rename_src_alloc = 0;
+ return;
+}
diff --git a/diffcore.h b/diffcore.h
new file mode 100644
index 000000000..a38acb13e
--- /dev/null
+++ b/diffcore.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#ifndef _DIFFCORE_H_
+#define _DIFFCORE_H_
+
+/* This header file is internal between diff.c and its diff transformers
+ * (e.g. diffcore-rename, diffcore-pickaxe). Never include this header
+ * in anything else.
+ */
+
+/* We internally use unsigned short as the score value,
+ * and rely on an int capable to hold 32-bits. -B can take
+ * -Bmerge_score/break_score format and the two scores are
+ * passed around in one int (high 16-bit for merge and low 16-bit
+ * for break).
+ */
+#define MAX_SCORE 60000
+#define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
+#define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%)*/
+#define DEFAULT_MERGE_SCORE 48000 /* maximum for break-merge to happen (80%)*/
+
+#define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */
+
+struct diff_filespec {
+ unsigned char sha1[20];
+ char *path;
+ void *data;
+ unsigned long size;
+ int xfrm_flags; /* for use by the xfrm */
+ unsigned short mode; /* file mode */
+ unsigned sha1_valid : 1; /* if true, use sha1 and trust mode;
+ * if false, use the name and read from
+ * the filesystem.
+ */
+#define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
+ unsigned should_free : 1; /* data should be free()'ed */
+ unsigned should_munmap : 1; /* data should be munmap()'ed */
+};
+
+extern struct diff_filespec *alloc_filespec(const char *);
+extern void fill_filespec(struct diff_filespec *, const unsigned char *,
+ unsigned short);
+
+extern int diff_populate_filespec(struct diff_filespec *, int);
+extern void diff_free_filespec_data(struct diff_filespec *);
+
+struct diff_filepair {
+ struct diff_filespec *one;
+ struct diff_filespec *two;
+ unsigned short int score;
+ char status; /* M C R N D U (see Documentation/diff-format.txt) */
+ unsigned source_stays : 1; /* all of R/C are copies */
+ unsigned broken_pair : 1;
+};
+#define DIFF_PAIR_UNMERGED(p) \
+ (!DIFF_FILE_VALID((p)->one) && !DIFF_FILE_VALID((p)->two))
+
+#define DIFF_PAIR_RENAME(p) (strcmp((p)->one->path, (p)->two->path))
+
+#define DIFF_PAIR_BROKEN(p) \
+ ( (!DIFF_FILE_VALID((p)->one) != !DIFF_FILE_VALID((p)->two)) && \
+ ((p)->broken_pair != 0) )
+
+#define DIFF_PAIR_TYPE_CHANGED(p) \
+ ((S_IFMT & (p)->one->mode) != (S_IFMT & (p)->two->mode))
+
+#define DIFF_PAIR_MODE_CHANGED(p) ((p)->one->mode != (p)->two->mode)
+
+extern void diff_free_filepair(struct diff_filepair *);
+
+extern int diff_unmodified_pair(struct diff_filepair *);
+
+struct diff_queue_struct {
+ struct diff_filepair **queue;
+ int alloc;
+ int nr;
+};
+
+extern struct diff_queue_struct diff_queued_diff;
+extern struct diff_filepair *diff_queue(struct diff_queue_struct *,
+ struct diff_filespec *,
+ struct diff_filespec *);
+extern void diff_q(struct diff_queue_struct *, struct diff_filepair *);
+
+extern void diffcore_pathspec(const char **pathspec);
+extern void diffcore_break(int);
+extern void diffcore_rename(struct diff_options *);
+extern void diffcore_merge_broken(void);
+extern void diffcore_pickaxe(const char *needle, int opts);
+extern void diffcore_order(const char *orderfile);
+
+#define DIFF_DEBUG 0
+#if DIFF_DEBUG
+void diff_debug_filespec(struct diff_filespec *, int, const char *);
+void diff_debug_filepair(const struct diff_filepair *, int);
+void diff_debug_queue(const char *, struct diff_queue_struct *);
+#else
+#define diff_debug_filespec(a,b,c) do {} while(0)
+#define diff_debug_filepair(a,b) do {} while(0)
+#define diff_debug_queue(a,b) do {} while(0)
+#endif
+
+#endif
diff --git a/entry.c b/entry.c
new file mode 100644
index 000000000..15b34eb6f
--- /dev/null
+++ b/entry.c
@@ -0,0 +1,156 @@
+#include <sys/types.h>
+#include <dirent.h>
+#include "cache.h"
+
+static void create_directories(const char *path, struct checkout *state)
+{
+ int len = strlen(path);
+ char *buf = xmalloc(len + 1);
+ const char *slash = path;
+
+ while ((slash = strchr(slash+1, '/')) != NULL) {
+ len = slash - path;
+ memcpy(buf, path, len);
+ buf[len] = 0;
+ if (mkdir(buf, 0777)) {
+ if (errno == EEXIST) {
+ struct stat st;
+ if (len > state->base_dir_len && state->force && !unlink(buf) && !mkdir(buf, 0777))
+ continue;
+ if (!stat(buf, &st) && S_ISDIR(st.st_mode))
+ continue; /* ok */
+ }
+ die("cannot create directory at %s", buf);
+ }
+ }
+ free(buf);
+}
+
+static void remove_subtree(const char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *de;
+ char pathbuf[PATH_MAX];
+ char *name;
+
+ if (!dir)
+ die("cannot opendir %s", path);
+ strcpy(pathbuf, path);
+ name = pathbuf + strlen(path);
+ *name++ = '/';
+ while ((de = readdir(dir)) != NULL) {
+ struct stat st;
+ if ((de->d_name[0] == '.') &&
+ ((de->d_name[1] == 0) ||
+ ((de->d_name[1] == '.') && de->d_name[2] == 0)))
+ continue;
+ strcpy(name, de->d_name);
+ if (lstat(pathbuf, &st))
+ die("cannot lstat %s", pathbuf);
+ if (S_ISDIR(st.st_mode))
+ remove_subtree(pathbuf);
+ else if (unlink(pathbuf))
+ die("cannot unlink %s", pathbuf);
+ }
+ closedir(dir);
+ if (rmdir(path))
+ die("cannot rmdir %s", path);
+}
+
+static int create_file(const char *path, unsigned int mode)
+{
+ mode = (mode & 0100) ? 0777 : 0666;
+ return open(path, O_WRONLY | O_TRUNC | O_CREAT | O_EXCL, mode);
+}
+
+static int write_entry(struct cache_entry *ce, const char *path, struct checkout *state)
+{
+ int fd;
+ void *new;
+ unsigned long size;
+ long wrote;
+ char type[20];
+ char target[1024];
+
+ new = read_sha1_file(ce->sha1, type, &size);
+ if (!new || strcmp(type, "blob")) {
+ if (new)
+ free(new);
+ return error("git-checkout-index: unable to read sha1 file of %s (%s)",
+ path, sha1_to_hex(ce->sha1));
+ }
+ switch (ntohl(ce->ce_mode) & S_IFMT) {
+ case S_IFREG:
+ fd = create_file(path, ntohl(ce->ce_mode));
+ if (fd < 0) {
+ free(new);
+ return error("git-checkout-index: unable to create file %s (%s)",
+ path, strerror(errno));
+ }
+ wrote = write(fd, new, size);
+ close(fd);
+ free(new);
+ if (wrote != size)
+ return error("git-checkout-index: unable to write file %s", path);
+ break;
+ case S_IFLNK:
+ memcpy(target, new, size);
+ target[size] = '\0';
+ if (symlink(target, path)) {
+ free(new);
+ return error("git-checkout-index: unable to create symlink %s (%s)",
+ path, strerror(errno));
+ }
+ free(new);
+ break;
+ default:
+ free(new);
+ return error("git-checkout-index: unknown file mode for %s", path);
+ }
+
+ if (state->refresh_cache) {
+ struct stat st;
+ lstat(ce->name, &st);
+ fill_stat_cache_info(ce, &st);
+ }
+ return 0;
+}
+
+int checkout_entry(struct cache_entry *ce, struct checkout *state)
+{
+ struct stat st;
+ static char path[MAXPATHLEN+1];
+ int len = state->base_dir_len;
+
+ memcpy(path, state->base_dir, len);
+ strcpy(path + len, ce->name);
+
+ if (!lstat(path, &st)) {
+ unsigned changed = ce_match_stat(ce, &st);
+ if (!changed)
+ return 0;
+ if (!state->force) {
+ if (!state->quiet)
+ fprintf(stderr, "git-checkout-index: %s already exists\n", path);
+ return -1;
+ }
+
+ /*
+ * We unlink the old file, to get the new one with the
+ * right permissions (including umask, which is nasty
+ * to emulate by hand - much easier to let the system
+ * just do the right thing)
+ */
+ unlink(path);
+ if (S_ISDIR(st.st_mode)) {
+ if (!state->force)
+ return error("%s is a directory", path);
+ remove_subtree(path);
+ }
+ } else if (state->not_new)
+ return 0;
+ create_directories(path, state);
+ return write_entry(ce, path, state);
+}
+
+
diff --git a/environment.c b/environment.c
new file mode 100644
index 000000000..b5026f126
--- /dev/null
+++ b/environment.c
@@ -0,0 +1,76 @@
+/*
+ * We put all the git config variables in this same object
+ * file, so that programs can link against the config parser
+ * without having to link against all the rest of git.
+ *
+ * In particular, no need to bring in libz etc unless needed,
+ * even if you might want to know where the git directory etc
+ * are.
+ */
+#include "cache.h"
+
+char git_default_email[MAX_GITNAME];
+char git_default_name[MAX_GITNAME];
+int trust_executable_bit = 1;
+int only_use_symrefs = 0;
+
+static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
+ *git_graft_file;
+static void setup_git_env(void)
+{
+ git_dir = getenv(GIT_DIR_ENVIRONMENT);
+ if (!git_dir)
+ git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+ git_object_dir = getenv(DB_ENVIRONMENT);
+ if (!git_object_dir) {
+ git_object_dir = xmalloc(strlen(git_dir) + 9);
+ sprintf(git_object_dir, "%s/objects", git_dir);
+ }
+ git_refs_dir = xmalloc(strlen(git_dir) + 6);
+ sprintf(git_refs_dir, "%s/refs", git_dir);
+ git_index_file = getenv(INDEX_ENVIRONMENT);
+ if (!git_index_file) {
+ git_index_file = xmalloc(strlen(git_dir) + 7);
+ sprintf(git_index_file, "%s/index", git_dir);
+ }
+ git_graft_file = getenv(GRAFT_ENVIRONMENT);
+ if (!git_graft_file)
+ git_graft_file = strdup(git_path("info/grafts"));
+}
+
+char *get_git_dir(void)
+{
+ if (!git_dir)
+ setup_git_env();
+ return git_dir;
+}
+
+char *get_object_directory(void)
+{
+ if (!git_object_dir)
+ setup_git_env();
+ return git_object_dir;
+}
+
+char *get_refs_directory(void)
+{
+ if (!git_refs_dir)
+ setup_git_env();
+ return git_refs_dir;
+}
+
+char *get_index_file(void)
+{
+ if (!git_index_file)
+ setup_git_env();
+ return git_index_file;
+}
+
+char *get_graft_file(void)
+{
+ if (!git_graft_file)
+ setup_git_env();
+ return git_graft_file;
+}
+
+
diff --git a/epoch.c b/epoch.c
new file mode 100644
index 000000000..db44f5ca9
--- /dev/null
+++ b/epoch.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (c) 2005, Jon Seymour
+ *
+ * For more information about epoch theory on which this module is based,
+ * refer to http://blackcubes.dyndns.org/epoch/. That web page defines
+ * terms such as "epoch" and "minimal, non-linear epoch" and provides rationales
+ * for some of the algorithms used here.
+ *
+ */
+#include <stdlib.h>
+
+/* Provides arbitrary precision integers required to accurately represent
+ * fractional mass: */
+#include <openssl/bn.h>
+
+#include "cache.h"
+#include "commit.h"
+#include "epoch.h"
+
+struct fraction {
+ BIGNUM numerator;
+ BIGNUM denominator;
+};
+
+#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
+
+static BN_CTX *context = NULL;
+static struct fraction *one = NULL;
+static struct fraction *zero = NULL;
+
+static BN_CTX *get_BN_CTX(void)
+{
+ if (!context) {
+ context = BN_CTX_new();
+ }
+ return context;
+}
+
+static struct fraction *new_zero(void)
+{
+ struct fraction *result = xmalloc(sizeof(*result));
+ BN_init(&result->numerator);
+ BN_init(&result->denominator);
+ BN_zero(&result->numerator);
+ BN_one(&result->denominator);
+ return result;
+}
+
+static void clear_fraction(struct fraction *fraction)
+{
+ BN_clear(&fraction->numerator);
+ BN_clear(&fraction->denominator);
+}
+
+static struct fraction *divide(struct fraction *result, struct fraction *fraction, int divisor)
+{
+ BIGNUM bn_divisor;
+
+ BN_init(&bn_divisor);
+ BN_set_word(&bn_divisor, divisor);
+
+ BN_copy(&result->numerator, &fraction->numerator);
+ BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());
+
+ BN_clear(&bn_divisor);
+ return result;
+}
+
+static struct fraction *init_fraction(struct fraction *fraction)
+{
+ BN_init(&fraction->numerator);
+ BN_init(&fraction->denominator);
+ BN_zero(&fraction->numerator);
+ BN_one(&fraction->denominator);
+ return fraction;
+}
+
+static struct fraction *get_one(void)
+{
+ if (!one) {
+ one = new_zero();
+ BN_one(&one->numerator);
+ }
+ return one;
+}
+
+static struct fraction *get_zero(void)
+{
+ if (!zero) {
+ zero = new_zero();
+ }
+ return zero;
+}
+
+static struct fraction *copy(struct fraction *to, struct fraction *from)
+{
+ BN_copy(&to->numerator, &from->numerator);
+ BN_copy(&to->denominator, &from->denominator);
+ return to;
+}
+
+static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)
+{
+ BIGNUM a, b, gcd;
+
+ BN_init(&a);
+ BN_init(&b);
+ BN_init(&gcd);
+
+ BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
+ BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
+ BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());
+ BN_add(&result->numerator, &a, &b);
+
+ BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());
+ BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());
+ BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());
+
+ BN_clear(&a);
+ BN_clear(&b);
+ BN_clear(&gcd);
+
+ return result;
+}
+
+static int compare(struct fraction *left, struct fraction *right)
+{
+ BIGNUM a, b;
+ int result;
+
+ BN_init(&a);
+ BN_init(&b);
+
+ BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
+ BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
+
+ result = BN_cmp(&a, &b);
+
+ BN_clear(&a);
+ BN_clear(&b);
+
+ return result;
+}
+
+struct mass_counter {
+ struct fraction seen;
+ struct fraction pending;
+};
+
+static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)
+{
+ struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));
+ memset(mass_counter, 0, sizeof(*mass_counter));
+
+ init_fraction(&mass_counter->seen);
+ init_fraction(&mass_counter->pending);
+
+ copy(&mass_counter->pending, pending);
+ copy(&mass_counter->seen, get_zero());
+
+ if (commit->object.util) {
+ die("multiple attempts to initialize mass counter for %s",
+ sha1_to_hex(commit->object.sha1));
+ }
+
+ commit->object.util = mass_counter;
+
+ return mass_counter;
+}
+
+static void free_mass_counter(struct mass_counter *counter)
+{
+ clear_fraction(&counter->seen);
+ clear_fraction(&counter->pending);
+ free(counter);
+}
+
+/*
+ * Finds the base commit of a list of commits.
+ *
+ * One property of the commit being searched for is that every commit reachable
+ * from the base commit is reachable from the commits in the starting list only
+ * via paths that include the base commit.
+ *
+ * This algorithm uses a conservation of mass approach to find the base commit.
+ *
+ * We start by injecting one unit of mass into the graph at each
+ * of the commits in the starting list. Injecting mass into a commit
+ * is achieved by adding to its pending mass counter and, if it is not already
+ * enqueued, enqueuing the commit in a list of pending commits, in latest
+ * commit date first order.
+ *
+ * The algorithm then preceeds to visit each commit in the pending queue.
+ * Upon each visit, the pending mass is added to the mass already seen for that
+ * commit and then divided into N equal portions, where N is the number of
+ * parents of the commit being visited. The divided portions are then injected
+ * into each of the parents.
+ *
+ * The algorithm continues until we discover a commit which has seen all the
+ * mass originally injected or until we run out of things to do.
+ *
+ * If we find a commit that has seen all the original mass, we have found
+ * the common base of all the commits in the starting list.
+ *
+ * The algorithm does _not_ depend on accurate timestamps for correct operation.
+ * However, reasonably sane (e.g. non-random) timestamps are required in order
+ * to prevent an exponential performance characteristic. The occasional
+ * timestamp inaccuracy will not dramatically affect performance but may
+ * result in more nodes being processed than strictly necessary.
+ *
+ * This procedure sets *boundary to the address of the base commit. It returns
+ * non-zero if, and only if, there was a problem parsing one of the
+ * commits discovered during the traversal.
+ */
+static int find_base_for_list(struct commit_list *list, struct commit **boundary)
+{
+ int ret = 0;
+ struct commit_list *cleaner = NULL;
+ struct commit_list *pending = NULL;
+ struct fraction injected;
+ init_fraction(&injected);
+ *boundary = NULL;
+
+ for (; list; list = list->next) {
+ struct commit *item = list->item;
+
+ if (!item->object.util) {
+ new_mass_counter(list->item, get_one());
+ add(&injected, &injected, get_one());
+
+ commit_list_insert(list->item, &cleaner);
+ commit_list_insert(list->item, &pending);
+ }
+ }
+
+ while (!*boundary && pending && !ret) {
+ struct commit *latest = pop_commit(&pending);
+ struct mass_counter *latest_node = (struct mass_counter *) latest->object.util;
+ int num_parents;
+
+ if ((ret = parse_commit(latest)))
+ continue;
+ add(&latest_node->seen, &latest_node->seen, &latest_node->pending);
+
+ num_parents = count_parents(latest);
+ if (num_parents) {
+ struct fraction distribution;
+ struct commit_list *parents;
+
+ divide(init_fraction(&distribution), &latest_node->pending, num_parents);
+
+ for (parents = latest->parents; parents; parents = parents->next) {
+ struct commit *parent = parents->item;
+ struct mass_counter *parent_node = (struct mass_counter *) parent->object.util;
+
+ if (!parent_node) {
+ parent_node = new_mass_counter(parent, &distribution);
+ insert_by_date(parent, &pending);
+ commit_list_insert(parent, &cleaner);
+ } else {
+ if (!compare(&parent_node->pending, get_zero()))
+ insert_by_date(parent, &pending);
+ add(&parent_node->pending, &parent_node->pending, &distribution);
+ }
+ }
+
+ clear_fraction(&distribution);
+ }
+
+ if (!compare(&latest_node->seen, &injected))
+ *boundary = latest;
+ copy(&latest_node->pending, get_zero());
+ }
+
+ while (cleaner) {
+ struct commit *next = pop_commit(&cleaner);
+ free_mass_counter((struct mass_counter *) next->object.util);
+ next->object.util = NULL;
+ }
+
+ if (pending)
+ free_commit_list(pending);
+
+ clear_fraction(&injected);
+ return ret;
+}
+
+
+/*
+ * Finds the base of an minimal, non-linear epoch, headed at head, by
+ * applying the find_base_for_list to a list consisting of the parents
+ */
+static int find_base(struct commit *head, struct commit **boundary)
+{
+ int ret = 0;
+ struct commit_list *pending = NULL;
+ struct commit_list *next;
+
+ for (next = head->parents; next; next = next->next) {
+ commit_list_insert(next->item, &pending);
+ }
+ ret = find_base_for_list(pending, boundary);
+ free_commit_list(pending);
+
+ return ret;
+}
+
+/*
+ * This procedure traverses to the boundary of the first epoch in the epoch
+ * sequence of the epoch headed at head_of_epoch. This is either the end of
+ * the maximal linear epoch or the base of a minimal non-linear epoch.
+ *
+ * The queue of pending nodes is sorted in reverse date order and each node
+ * is currently in the queue at most once.
+ */
+static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)
+{
+ int ret;
+ struct commit *item = head_of_epoch;
+
+ ret = parse_commit(item);
+ if (ret)
+ return ret;
+
+ if (HAS_EXACTLY_ONE_PARENT(item)) {
+ /*
+ * We are at the start of a maximimal linear epoch.
+ * Traverse to the end.
+ */
+ while (HAS_EXACTLY_ONE_PARENT(item) && !ret) {
+ item = item->parents->item;
+ ret = parse_commit(item);
+ }
+ *boundary = item;
+
+ } else {
+ /*
+ * Otherwise, we are at the start of a minimal, non-linear
+ * epoch - find the common base of all parents.
+ */
+ ret = find_base(item, boundary);
+ }
+
+ return ret;
+}
+
+/*
+ * Returns non-zero if parent is known to be a parent of child.
+ */
+static int is_parent_of(struct commit *parent, struct commit *child)
+{
+ struct commit_list *parents;
+ for (parents = child->parents; parents; parents = parents->next) {
+ if (!memcmp(parent->object.sha1, parents->item->object.sha1,
+ sizeof(parents->item->object.sha1)))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Pushes an item onto the merge order stack. If the top of the stack is
+ * marked as being a possible "break", we check to see whether it actually
+ * is a break.
+ */
+static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)
+{
+ struct commit_list *top = *stack;
+ if (top && (top->item->object.flags & DISCONTINUITY)) {
+ if (is_parent_of(top->item, item)) {
+ top->item->object.flags &= ~DISCONTINUITY;
+ }
+ }
+ commit_list_insert(item, stack);
+}
+
+/*
+ * Marks all interesting, visited commits reachable from this commit
+ * as uninteresting. We stop recursing when we reach the epoch boundary,
+ * an unvisited node or a node that has already been marking uninteresting.
+ *
+ * This doesn't actually mark all ancestors between the start node and the
+ * epoch boundary uninteresting, but does ensure that they will eventually
+ * be marked uninteresting when the main sort_first_epoch() traversal
+ * eventually reaches them.
+ */
+static void mark_ancestors_uninteresting(struct commit *commit)
+{
+ unsigned int flags = commit->object.flags;
+ int visited = flags & VISITED;
+ int boundary = flags & BOUNDARY;
+ int uninteresting = flags & UNINTERESTING;
+ struct commit_list *next;
+
+ commit->object.flags |= UNINTERESTING;
+
+ /*
+ * We only need to recurse if
+ * we are not on the boundary and
+ * we have not already been marked uninteresting and
+ * we have already been visited.
+ *
+ * The main sort_first_epoch traverse will mark unreachable
+ * all uninteresting, unvisited parents as they are visited
+ * so there is no need to duplicate that traversal here.
+ *
+ * Similarly, if we are already marked uninteresting
+ * then either all ancestors have already been marked
+ * uninteresting or will be once the sort_first_epoch
+ * traverse reaches them.
+ */
+
+ if (uninteresting || boundary || !visited)
+ return;
+
+ for (next = commit->parents; next; next = next->next)
+ mark_ancestors_uninteresting(next->item);
+}
+
+/*
+ * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head
+ * into merge order.
+ */
+static void sort_first_epoch(struct commit *head, struct commit_list **stack)
+{
+ struct commit_list *parents;
+
+ head->object.flags |= VISITED;
+
+ /*
+ * TODO: By sorting the parents in a different order, we can alter the
+ * merge order to show contemporaneous changes in parallel branches
+ * occurring after "local" changes. This is useful for a developer
+ * when a developer wants to see all changes that were incorporated
+ * into the same merge as her own changes occur after her own
+ * changes.
+ */
+
+ for (parents = head->parents; parents; parents = parents->next) {
+ struct commit *parent = parents->item;
+
+ if (head->object.flags & UNINTERESTING) {
+ /*
+ * Propagates the uninteresting bit to all parents.
+ * if we have already visited this parent, then
+ * the uninteresting bit will be propagated to each
+ * reachable commit that is still not marked
+ * uninteresting and won't otherwise be reached.
+ */
+ mark_ancestors_uninteresting(parent);
+ }
+
+ if (!(parent->object.flags & VISITED)) {
+ if (parent->object.flags & BOUNDARY) {
+ if (*stack) {
+ die("something else is on the stack - %s",
+ sha1_to_hex((*stack)->item->object.sha1));
+ }
+ push_onto_merge_order_stack(stack, parent);
+ parent->object.flags |= VISITED;
+
+ } else {
+ sort_first_epoch(parent, stack);
+ if (parents) {
+ /*
+ * This indicates a possible
+ * discontinuity it may not be be
+ * actual discontinuity if the head
+ * of parent N happens to be the tail
+ * of parent N+1.
+ *
+ * The next push onto the stack will
+ * resolve the question.
+ */
+ (*stack)->item->object.flags |= DISCONTINUITY;
+ }
+ }
+ }
+ }
+
+ push_onto_merge_order_stack(stack, head);
+}
+
+/*
+ * Emit the contents of the stack.
+ *
+ * The stack is freed and replaced by NULL.
+ *
+ * Sets the return value to STOP if no further output should be generated.
+ */
+static int emit_stack(struct commit_list **stack, emitter_func emitter, int include_last)
+{
+ unsigned int seen = 0;
+ int action = CONTINUE;
+
+ while (*stack && (action != STOP)) {
+ struct commit *next = pop_commit(stack);
+ seen |= next->object.flags;
+ if (*stack || include_last) {
+ if (!*stack)
+ next->object.flags |= BOUNDARY;
+ action = emitter(next);
+ }
+ }
+
+ if (*stack) {
+ free_commit_list(*stack);
+ *stack = NULL;
+ }
+
+ return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;
+}
+
+/*
+ * Sorts an arbitrary epoch into merge order by sorting each epoch
+ * of its epoch sequence into order.
+ *
+ * Note: this algorithm currently leaves traces of its execution in the
+ * object flags of nodes it discovers. This should probably be fixed.
+ */
+static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
+{
+ struct commit *next = head_of_epoch;
+ int ret = 0;
+ int action = CONTINUE;
+
+ ret = parse_commit(head_of_epoch);
+
+ next->object.flags |= BOUNDARY;
+
+ while (next && next->parents && !ret && (action != STOP)) {
+ struct commit *base = NULL;
+
+ ret = find_next_epoch_boundary(next, &base);
+ if (ret)
+ return ret;
+ next->object.flags |= BOUNDARY;
+ if (base)
+ base->object.flags |= BOUNDARY;
+
+ if (HAS_EXACTLY_ONE_PARENT(next)) {
+ while (HAS_EXACTLY_ONE_PARENT(next)
+ && (action != STOP)
+ && !ret) {
+ if (next->object.flags & UNINTERESTING) {
+ action = STOP;
+ } else {
+ action = emitter(next);
+ }
+ if (action != STOP) {
+ next = next->parents->item;
+ ret = parse_commit(next);
+ }
+ }
+
+ } else {
+ struct commit_list *stack = NULL;
+ sort_first_epoch(next, &stack);
+ action = emit_stack(&stack, emitter, (base == NULL));
+ next = base;
+ }
+ }
+
+ if (next && (action != STOP) && !ret) {
+ emitter(next);
+ }
+
+ return ret;
+}
+
+/*
+ * Sorts the nodes reachable from a starting list in merge order, we
+ * first find the base for the starting list and then sort all nodes
+ * in this subgraph using the sort_first_epoch algorithm. Once we have
+ * reached the base we can continue sorting using sort_in_merge_order.
+ */
+int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
+{
+ struct commit_list *stack = NULL;
+ struct commit *base;
+ int ret = 0;
+ int action = CONTINUE;
+ struct commit_list *reversed = NULL;
+
+ for (; list; list = list->next)
+ commit_list_insert(list->item, &reversed);
+
+ if (!reversed)
+ return ret;
+ else if (!reversed->next) {
+ /*
+ * If there is only one element in the list, we can sort it
+ * using sort_in_merge_order.
+ */
+ base = reversed->item;
+ } else {
+ /*
+ * Otherwise, we search for the base of the list.
+ */
+ ret = find_base_for_list(reversed, &base);
+ if (ret)
+ return ret;
+ if (base)
+ base->object.flags |= BOUNDARY;
+
+ while (reversed) {
+ struct commit * next = pop_commit(&reversed);
+
+ if (!(next->object.flags & VISITED) && next!=base) {
+ sort_first_epoch(next, &stack);
+ if (reversed) {
+ /*
+ * If we have more commits
+ * to push, then the first
+ * push for the next parent may
+ * (or may * not) represent a
+ * discontinuity with respect
+ * to the parent currently on
+ * the top of the stack.
+ *
+ * Mark it for checking here,
+ * and check it with the next
+ * push. See sort_first_epoch()
+ * for more details.
+ */
+ stack->item->object.flags |= DISCONTINUITY;
+ }
+ }
+ }
+
+ action = emit_stack(&stack, emitter, (base==NULL));
+ }
+
+ if (base && (action != STOP)) {
+ ret = sort_in_merge_order(base, emitter);
+ }
+
+ return ret;
+}
diff --git a/epoch.h b/epoch.h
new file mode 100644
index 000000000..7493d5a24
--- /dev/null
+++ b/epoch.h
@@ -0,0 +1,21 @@
+#ifndef EPOCH_H
+#define EPOCH_H
+
+
+// return codes for emitter_func
+#define STOP 0
+#define CONTINUE 1
+#define DO 2
+typedef int (*emitter_func) (struct commit *);
+
+int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
+
+/* Low bits are used by rev-list */
+#define UNINTERESTING (1u<<10)
+#define BOUNDARY (1u<<11)
+#define VISITED (1u<<12)
+#define DISCONTINUITY (1u<<13)
+#define LAST_EPOCH_FLAG (1u<<14)
+
+
+#endif /* EPOCH_H */
diff --git a/fetch-pack.c b/fetch-pack.c
new file mode 100644
index 000000000..656598266
--- /dev/null
+++ b/fetch-pack.c
@@ -0,0 +1,476 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "commit.h"
+#include "tag.h"
+#include <time.h>
+#include <sys/wait.h>
+
+static int quiet;
+static int verbose;
+static const char fetch_pack_usage[] =
+"git-fetch-pack [-q] [-v] [--exec=upload-pack] [host:]directory <refs>...";
+static const char *exec = "git-upload-pack";
+
+#define COMPLETE (1U << 0)
+#define COMMON (1U << 1)
+#define COMMON_REF (1U << 2)
+#define SEEN (1U << 3)
+#define POPPED (1U << 4)
+
+static struct commit_list *rev_list = NULL;
+static int non_common_revs = 0, multi_ack = 0;
+
+static void rev_list_push(struct commit *commit, int mark)
+{
+ if (!(commit->object.flags & mark)) {
+ commit->object.flags |= mark;
+
+ if (!(commit->object.parsed))
+ parse_commit(commit);
+
+ insert_by_date(commit, &rev_list);
+
+ if (!(commit->object.flags & COMMON))
+ non_common_revs++;
+ }
+}
+
+static int rev_list_insert_ref(const char *path, const unsigned char *sha1)
+{
+ struct object *o = deref_tag(parse_object(sha1), path, 0);
+
+ if (o && o->type == commit_type)
+ rev_list_push((struct commit *)o, SEEN);
+
+ return 0;
+}
+
+/*
+ This function marks a rev and its ancestors as common.
+ In some cases, it is desirable to mark only the ancestors (for example
+ when only the server does not yet know that they are common).
+*/
+
+static void mark_common(struct commit *commit,
+ int ancestors_only, int dont_parse)
+{
+ if (commit != NULL && !(commit->object.flags & COMMON)) {
+ struct object *o = (struct object *)commit;
+
+ if (!ancestors_only)
+ o->flags |= COMMON;
+
+ if (!(o->flags & SEEN))
+ rev_list_push(commit, SEEN);
+ else {
+ struct commit_list *parents;
+
+ if (!ancestors_only && !(o->flags & POPPED))
+ non_common_revs--;
+ if (!o->parsed && !dont_parse)
+ parse_commit(commit);
+
+ for (parents = commit->parents;
+ parents;
+ parents = parents->next)
+ mark_common(parents->item, 0, dont_parse);
+ }
+ }
+}
+
+/*
+ Get the next rev to send, ignoring the common.
+*/
+
+static const unsigned char* get_rev()
+{
+ struct commit *commit = NULL;
+
+ while (commit == NULL) {
+ unsigned int mark;
+ struct commit_list* parents;
+
+ if (rev_list == NULL || non_common_revs == 0)
+ return NULL;
+
+ commit = rev_list->item;
+ if (!(commit->object.parsed))
+ parse_commit(commit);
+ commit->object.flags |= POPPED;
+ if (!(commit->object.flags & COMMON))
+ non_common_revs--;
+
+ parents = commit->parents;
+
+ if (commit->object.flags & COMMON) {
+ /* do not send "have", and ignore ancestors */
+ commit = NULL;
+ mark = COMMON | SEEN;
+ } else if (commit->object.flags & COMMON_REF)
+ /* send "have", and ignore ancestors */
+ mark = COMMON | SEEN;
+ else
+ /* send "have", also for its ancestors */
+ mark = SEEN;
+
+ while (parents) {
+ if (!(parents->item->object.flags & SEEN))
+ rev_list_push(parents->item, mark);
+ if (mark & COMMON)
+ mark_common(parents->item, 1, 0);
+ parents = parents->next;
+ }
+
+ rev_list = rev_list->next;
+ }
+
+ return commit->object.sha1;
+}
+
+static int find_common(int fd[2], unsigned char *result_sha1,
+ struct ref *refs)
+{
+ int fetching;
+ int count = 0, flushes = 0, retval;
+ const unsigned char *sha1;
+
+ for_each_ref(rev_list_insert_ref);
+
+ fetching = 0;
+ for ( ; refs ; refs = refs->next) {
+ unsigned char *remote = refs->old_sha1;
+ struct object *o;
+
+ /*
+ * If that object is complete (i.e. it is an ancestor of a
+ * local ref), we tell them we have it but do not have to
+ * tell them about its ancestors, which they already know
+ * about.
+ *
+ * We use lookup_object here because we are only
+ * interested in the case we *know* the object is
+ * reachable and we have already scanned it.
+ */
+ if (((o = lookup_object(remote)) != NULL) &&
+ (o->flags & COMPLETE)) {
+ continue;
+ }
+
+ packet_write(fd[1], "want %s%s\n", sha1_to_hex(remote),
+ multi_ack ? " multi_ack" : "");
+ fetching++;
+ }
+ packet_flush(fd[1]);
+ if (!fetching)
+ return 1;
+
+ flushes = 0;
+ retval = -1;
+ while ((sha1 = get_rev())) {
+ packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+ if (verbose)
+ fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
+ if (!(31 & ++count)) {
+ int ack;
+
+ packet_flush(fd[1]);
+ flushes++;
+
+ /*
+ * We keep one window "ahead" of the other side, and
+ * will wait for an ACK only on the next one
+ */
+ if (count == 32)
+ continue;
+
+ do {
+ ack = get_ack(fd[0], result_sha1);
+ if (verbose && ack)
+ fprintf(stderr, "got ack %d %s\n", ack,
+ sha1_to_hex(result_sha1));
+ if (ack == 1) {
+ flushes = 0;
+ multi_ack = 0;
+ retval = 0;
+ goto done;
+ } else if (ack == 2) {
+ struct commit *commit =
+ lookup_commit(result_sha1);
+ mark_common(commit, 0, 1);
+ retval = 0;
+ }
+ } while (ack);
+ flushes--;
+ }
+ }
+done:
+ packet_write(fd[1], "done\n");
+ if (verbose)
+ fprintf(stderr, "done\n");
+ if (retval != 0) {
+ multi_ack = 0;
+ flushes++;
+ }
+ while (flushes || multi_ack) {
+ int ack = get_ack(fd[0], result_sha1);
+ if (ack) {
+ if (verbose)
+ fprintf(stderr, "got ack (%d) %s\n", ack,
+ sha1_to_hex(result_sha1));
+ if (ack == 1)
+ return 0;
+ multi_ack = 1;
+ continue;
+ }
+ flushes--;
+ }
+ return retval;
+}
+
+static struct commit_list *complete = NULL;
+
+static int mark_complete(const char *path, const unsigned char *sha1)
+{
+ struct object *o = parse_object(sha1);
+
+ while (o && o->type == tag_type) {
+ struct tag *t = (struct tag *) o;
+ if (!t->tagged)
+ break; /* broken repository */
+ o->flags |= COMPLETE;
+ o = parse_object(t->tagged->sha1);
+ }
+ if (o && o->type == commit_type) {
+ struct commit *commit = (struct commit *)o;
+ commit->object.flags |= COMPLETE;
+ insert_by_date(commit, &complete);
+ }
+ return 0;
+}
+
+static void mark_recent_complete_commits(unsigned long cutoff)
+{
+ while (complete && cutoff <= complete->item->date) {
+ if (verbose)
+ fprintf(stderr, "Marking %s as complete\n",
+ sha1_to_hex(complete->item->object.sha1));
+ pop_most_recent_commit(&complete, COMPLETE);
+ }
+}
+
+static void filter_refs(struct ref **refs, int nr_match, char **match)
+{
+ struct ref *prev, *current, *next;
+
+ if (!nr_match)
+ return;
+
+ for (prev = NULL, current = *refs; current; current = next) {
+ next = current->next;
+ if ((!memcmp(current->name, "refs/", 5) &&
+ check_ref_format(current->name + 5)) ||
+ !path_match(current->name, nr_match, match)) {
+ if (prev == NULL)
+ *refs = next;
+ else
+ prev->next = next;
+ free(current);
+ } else
+ prev = current;
+ }
+}
+
+static int everything_local(struct ref **refs, int nr_match, char **match)
+{
+ struct ref *ref;
+ int retval;
+ unsigned long cutoff = 0;
+
+ track_object_refs = 0;
+ save_commit_buffer = 0;
+
+ for (ref = *refs; ref; ref = ref->next) {
+ struct object *o;
+
+ o = parse_object(ref->old_sha1);
+ if (!o)
+ continue;
+
+ /* We already have it -- which may mean that we were
+ * in sync with the other side at some time after
+ * that (it is OK if we guess wrong here).
+ */
+ if (o->type == commit_type) {
+ struct commit *commit = (struct commit *)o;
+ if (!cutoff || cutoff < commit->date)
+ cutoff = commit->date;
+ }
+ }
+
+ for_each_ref(mark_complete);
+ if (cutoff)
+ mark_recent_complete_commits(cutoff);
+
+ /*
+ * Mark all complete remote refs as common refs.
+ * Don't mark them common yet; the server has to be told so first.
+ */
+ for (ref = *refs; ref; ref = ref->next) {
+ struct object *o = deref_tag(lookup_object(ref->old_sha1),
+ NULL, 0);
+
+ if (!o || o->type != commit_type || !(o->flags & COMPLETE))
+ continue;
+
+ if (!(o->flags & SEEN)) {
+ rev_list_push((struct commit *)o, COMMON_REF | SEEN);
+
+ mark_common((struct commit *)o, 1, 1);
+ }
+ }
+
+ filter_refs(refs, nr_match, match);
+
+ for (retval = 1, ref = *refs; ref ; ref = ref->next) {
+ const unsigned char *remote = ref->old_sha1;
+ unsigned char local[20];
+ struct object *o;
+
+ o = lookup_object(remote);
+ if (!o || !(o->flags & COMPLETE)) {
+ retval = 0;
+ if (!verbose)
+ continue;
+ fprintf(stderr,
+ "want %s (%s)\n", sha1_to_hex(remote),
+ ref->name);
+ continue;
+ }
+
+ memcpy(ref->new_sha1, local, 20);
+ if (!verbose)
+ continue;
+ fprintf(stderr,
+ "already have %s (%s)\n", sha1_to_hex(remote),
+ ref->name);
+ }
+ return retval;
+}
+
+static int fetch_pack(int fd[2], int nr_match, char **match)
+{
+ struct ref *ref;
+ unsigned char sha1[20];
+ int status;
+ pid_t pid;
+
+ get_remote_heads(fd[0], &ref, 0, NULL, 0);
+ if (server_supports("multi_ack")) {
+ if (verbose)
+ fprintf(stderr, "Server supports multi_ack\n");
+ multi_ack = 1;
+ }
+ if (!ref) {
+ packet_flush(fd[1]);
+ die("no matching remote head");
+ }
+ if (everything_local(&ref, nr_match, match)) {
+ packet_flush(fd[1]);
+ goto all_done;
+ }
+ if (find_common(fd, sha1, ref) < 0)
+ fprintf(stderr, "warning: no common commits\n");
+ pid = fork();
+ if (pid < 0)
+ die("git-fetch-pack: unable to fork off git-unpack-objects");
+ if (!pid) {
+ dup2(fd[0], 0);
+ close(fd[0]);
+ close(fd[1]);
+ execlp("git-unpack-objects", "git-unpack-objects",
+ quiet ? "-q" : NULL, NULL);
+ die("git-unpack-objects exec failed");
+ }
+ close(fd[0]);
+ close(fd[1]);
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno != EINTR)
+ die("waiting for git-unpack-objects: %s", strerror(errno));
+ }
+ if (WIFEXITED(status)) {
+ int code = WEXITSTATUS(status);
+ if (code)
+ die("git-unpack-objects died with error code %d", code);
+all_done:
+ while (ref) {
+ printf("%s %s\n",
+ sha1_to_hex(ref->old_sha1), ref->name);
+ ref = ref->next;
+ }
+ return 0;
+ }
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+ die("git-unpack-objects died of signal %d", sig);
+ }
+ die("Sherlock Holmes! git-unpack-objects died of unnatural causes %d!", status);
+}
+
+int main(int argc, char **argv)
+{
+ int i, ret, nr_heads;
+ char *dest = NULL, **heads;
+ int fd[2];
+ pid_t pid;
+
+ nr_heads = 0;
+ heads = NULL;
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strncmp("--exec=", arg, 7)) {
+ exec = arg + 7;
+ continue;
+ }
+ if (!strcmp("-q", arg)) {
+ quiet = 1;
+ continue;
+ }
+ if (!strcmp("-v", arg)) {
+ verbose = 1;
+ continue;
+ }
+ usage(fetch_pack_usage);
+ }
+ dest = arg;
+ heads = argv + i + 1;
+ nr_heads = argc - i - 1;
+ break;
+ }
+ if (!dest)
+ usage(fetch_pack_usage);
+ pid = git_connect(fd, dest, exec);
+ if (pid < 0)
+ return 1;
+ ret = fetch_pack(fd, nr_heads, heads);
+ close(fd[0]);
+ close(fd[1]);
+ finish_connect(pid);
+
+ if (!ret && nr_heads) {
+ /* If the heads to pull were given, we should have
+ * consumed all of them by matching the remote.
+ * Otherwise, 'git-fetch remote no-such-ref' would
+ * silently succeed without issuing an error.
+ */
+ for (i = 0; i < nr_heads; i++)
+ if (heads[i] && heads[i][0]) {
+ error("no such remote ref %s", heads[i]);
+ ret = 1;
+ }
+ }
+
+ return ret;
+}
diff --git a/fetch.c b/fetch.c
new file mode 100644
index 000000000..73bde07ae
--- /dev/null
+++ b/fetch.c
@@ -0,0 +1,238 @@
+#include "fetch.h"
+
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "tag.h"
+#include "blob.h"
+#include "refs.h"
+
+const char *write_ref = NULL;
+
+const unsigned char *current_ref = NULL;
+
+int get_tree = 0;
+int get_history = 0;
+int get_all = 0;
+int get_verbosely = 0;
+int get_recover = 0;
+static unsigned char current_commit_sha1[20];
+
+void pull_say(const char *fmt, const char *hex)
+{
+ if (get_verbosely)
+ fprintf(stderr, fmt, hex);
+}
+
+static void report_missing(const char *what, const unsigned char *missing)
+{
+ char missing_hex[41];
+
+ strcpy(missing_hex, sha1_to_hex(missing));;
+ fprintf(stderr,
+ "Cannot obtain needed %s %s\nwhile processing commit %s.\n",
+ what, missing_hex, sha1_to_hex(current_commit_sha1));
+}
+
+static int process(struct object *obj);
+
+static int process_tree(struct tree *tree)
+{
+ struct tree_entry_list *entry;
+
+ if (parse_tree(tree))
+ return -1;
+
+ entry = tree->entries;
+ tree->entries = NULL;
+ while (entry) {
+ struct tree_entry_list *next = entry->next;
+ if (process(entry->item.any))
+ return -1;
+ free(entry->name);
+ free(entry);
+ entry = next;
+ }
+ return 0;
+}
+
+#define COMPLETE (1U << 0)
+#define SEEN (1U << 1)
+#define TO_SCAN (1U << 2)
+
+static struct commit_list *complete = NULL;
+
+static int process_commit(struct commit *commit)
+{
+ if (parse_commit(commit))
+ return -1;
+
+ while (complete && complete->item->date >= commit->date) {
+ pop_most_recent_commit(&complete, COMPLETE);
+ }
+
+ if (commit->object.flags & COMPLETE)
+ return 0;
+
+ memcpy(current_commit_sha1, commit->object.sha1, 20);
+
+ pull_say("walk %s\n", sha1_to_hex(commit->object.sha1));
+
+ if (get_tree) {
+ if (process(&commit->tree->object))
+ return -1;
+ if (!get_all)
+ get_tree = 0;
+ }
+ if (get_history) {
+ struct commit_list *parents = commit->parents;
+ for (; parents; parents = parents->next) {
+ if (process(&parents->item->object))
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int process_tag(struct tag *tag)
+{
+ if (parse_tag(tag))
+ return -1;
+ return process(tag->tagged);
+}
+
+static struct object_list *process_queue = NULL;
+static struct object_list **process_queue_end = &process_queue;
+
+static int process_object(struct object *obj)
+{
+ if (obj->type == commit_type) {
+ if (process_commit((struct commit *)obj))
+ return -1;
+ return 0;
+ }
+ if (obj->type == tree_type) {
+ if (process_tree((struct tree *)obj))
+ return -1;
+ return 0;
+ }
+ if (obj->type == blob_type) {
+ return 0;
+ }
+ if (obj->type == tag_type) {
+ if (process_tag((struct tag *)obj))
+ return -1;
+ return 0;
+ }
+ return error("Unable to determine requirements "
+ "of type %s for %s",
+ obj->type, sha1_to_hex(obj->sha1));
+}
+
+static int process(struct object *obj)
+{
+ if (obj->flags & SEEN)
+ return 0;
+ obj->flags |= SEEN;
+
+ if (has_sha1_file(obj->sha1)) {
+ /* We already have it, so we should scan it now. */
+ obj->flags |= TO_SCAN;
+ } else {
+ if (obj->flags & COMPLETE)
+ return 0;
+ prefetch(obj->sha1);
+ }
+
+ object_list_insert(obj, process_queue_end);
+ process_queue_end = &(*process_queue_end)->next;
+ return 0;
+}
+
+static int loop(void)
+{
+ struct object_list *elem;
+
+ while (process_queue) {
+ struct object *obj = process_queue->item;
+ elem = process_queue;
+ process_queue = elem->next;
+ free(elem);
+ if (!process_queue)
+ process_queue_end = &process_queue;
+
+ /* If we are not scanning this object, we placed it in
+ * the queue because we needed to fetch it first.
+ */
+ if (! (obj->flags & TO_SCAN)) {
+ if (fetch(obj->sha1)) {
+ report_missing(obj->type
+ ? obj->type
+ : "object", obj->sha1);
+ return -1;
+ }
+ }
+ if (!obj->type)
+ parse_object(obj->sha1);
+ if (process_object(obj))
+ return -1;
+ }
+ return 0;
+}
+
+static int interpret_target(char *target, unsigned char *sha1)
+{
+ if (!get_sha1_hex(target, sha1))
+ return 0;
+ if (!check_ref_format(target)) {
+ if (!fetch_ref(target, sha1)) {
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int mark_complete(const char *path, const unsigned char *sha1)
+{
+ struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ if (commit) {
+ commit->object.flags |= COMPLETE;
+ insert_by_date(commit, &complete);
+ }
+ return 0;
+}
+
+int pull(char *target)
+{
+ unsigned char sha1[20];
+ int fd = -1;
+
+ save_commit_buffer = 0;
+ track_object_refs = 0;
+ if (write_ref && current_ref) {
+ fd = lock_ref_sha1(write_ref, current_ref);
+ if (fd < 0)
+ return -1;
+ }
+
+ if (!get_recover) {
+ for_each_ref(mark_complete);
+ }
+
+ if (interpret_target(target, sha1))
+ return error("Could not interpret %s as something to pull",
+ target);
+ if (process(lookup_unknown_object(sha1)))
+ return -1;
+ if (loop())
+ return -1;
+
+ if (write_ref) {
+ if (current_ref) {
+ write_ref_sha1(write_ref, fd, sha1);
+ } else {
+ write_ref_sha1_unlocked(write_ref, sha1);
+ }
+ }
+ return 0;
+}
diff --git a/fetch.h b/fetch.h
new file mode 100644
index 000000000..9837a3d03
--- /dev/null
+++ b/fetch.h
@@ -0,0 +1,51 @@
+#ifndef PULL_H
+#define PULL_H
+
+/*
+ * Fetch object given SHA1 from the remote, and store it locally under
+ * GIT_OBJECT_DIRECTORY. Return 0 on success, -1 on failure. To be
+ * provided by the particular implementation.
+ */
+extern int fetch(unsigned char *sha1);
+
+/*
+ * Fetch the specified object and store it locally; fetch() will be
+ * called later to determine success. To be provided by the particular
+ * implementation.
+ */
+extern void prefetch(unsigned char *sha1);
+
+/*
+ * Fetch ref (relative to $GIT_DIR/refs) from the remote, and store
+ * the 20-byte SHA1 in sha1. Return 0 on success, -1 on failure. To
+ * be provided by the particular implementation.
+ */
+extern int fetch_ref(char *ref, unsigned char *sha1);
+
+/* If set, the ref filename to write the target value to. */
+extern const char *write_ref;
+
+/* If set, the hash that the current value of write_ref must be. */
+extern const unsigned char *current_ref;
+
+/* Set to fetch the target tree. */
+extern int get_tree;
+
+/* Set to fetch the commit history. */
+extern int get_history;
+
+/* Set to fetch the trees in the commit history. */
+extern int get_all;
+
+/* Set to be verbose */
+extern int get_verbosely;
+
+/* Set to check on all reachable objects. */
+extern int get_recover;
+
+/* Report what we got under get_verbosely */
+extern void pull_say(const char *, const char *);
+
+extern int pull(char *target);
+
+#endif /* PULL_H */
diff --git a/fsck-objects.c b/fsck-objects.c
new file mode 100644
index 000000000..0433a1d0d
--- /dev/null
+++ b/fsck-objects.c
@@ -0,0 +1,550 @@
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "cache.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "tag.h"
+#include "refs.h"
+#include "pack.h"
+
+#define REACHABLE 0x0001
+
+static int show_root = 0;
+static int show_tags = 0;
+static int show_unreachable = 0;
+static int standalone = 0;
+static int check_full = 0;
+static int check_strict = 0;
+static int keep_cache_objects = 0;
+static unsigned char head_sha1[20];
+
+
+static void objreport(struct object *obj, const char *severity,
+ const char *err, va_list params)
+{
+ fprintf(stderr, "%s in %s %s: ",
+ severity, obj->type, sha1_to_hex(obj->sha1));
+ vfprintf(stderr, err, params);
+ fputs("\n", stderr);
+}
+
+static int objerror(struct object *obj, const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ objreport(obj, "error", err, params);
+ va_end(params);
+ return -1;
+}
+
+static int objwarning(struct object *obj, const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ objreport(obj, "warning", err, params);
+ va_end(params);
+ return -1;
+}
+
+
+static void check_connectivity(void)
+{
+ int i;
+
+ /* Look up all the requirements, warn about missing objects.. */
+ for (i = 0; i < nr_objs; i++) {
+ struct object *obj = objs[i];
+
+ if (!obj->parsed) {
+ if (!standalone && has_sha1_file(obj->sha1))
+ ; /* it is in pack */
+ else
+ printf("missing %s %s\n",
+ obj->type, sha1_to_hex(obj->sha1));
+ continue;
+ }
+
+ if (obj->refs) {
+ const struct object_refs *refs = obj->refs;
+ unsigned j;
+ for (j = 0; j < refs->count; j++) {
+ struct object *ref = refs->ref[j];
+ if (ref->parsed ||
+ (!standalone && has_sha1_file(ref->sha1)))
+ continue;
+ printf("broken link from %7s %s\n",
+ obj->type, sha1_to_hex(obj->sha1));
+ printf(" to %7s %s\n",
+ ref->type, sha1_to_hex(ref->sha1));
+ }
+ }
+
+ if (show_unreachable && !(obj->flags & REACHABLE)) {
+ printf("unreachable %s %s\n",
+ obj->type, sha1_to_hex(obj->sha1));
+ continue;
+ }
+
+ if (!obj->used) {
+ printf("dangling %s %s\n", obj->type,
+ sha1_to_hex(obj->sha1));
+ }
+ }
+}
+
+/*
+ * The entries in a tree are ordered in the _path_ order,
+ * which means that a directory entry is ordered by adding
+ * a slash to the end of it.
+ *
+ * So a directory called "a" is ordered _after_ a file
+ * called "a.c", because "a/" sorts after "a.c".
+ */
+#define TREE_UNORDERED (-1)
+#define TREE_HAS_DUPS (-2)
+
+static int verify_ordered(struct tree_entry_list *a, struct tree_entry_list *b)
+{
+ int len1 = strlen(a->name);
+ int len2 = strlen(b->name);
+ int len = len1 < len2 ? len1 : len2;
+ unsigned char c1, c2;
+ int cmp;
+
+ cmp = memcmp(a->name, b->name, len);
+ if (cmp < 0)
+ return 0;
+ if (cmp > 0)
+ return TREE_UNORDERED;
+
+ /*
+ * Ok, the first <len> characters are the same.
+ * Now we need to order the next one, but turn
+ * a '\0' into a '/' for a directory entry.
+ */
+ c1 = a->name[len];
+ c2 = b->name[len];
+ if (!c1 && !c2)
+ /*
+ * git-write-tree used to write out a nonsense tree that has
+ * entries with the same name, one blob and one tree. Make
+ * sure we do not have duplicate entries.
+ */
+ return TREE_HAS_DUPS;
+ if (!c1 && a->directory)
+ c1 = '/';
+ if (!c2 && b->directory)
+ c2 = '/';
+ return c1 < c2 ? 0 : TREE_UNORDERED;
+}
+
+static int fsck_tree(struct tree *item)
+{
+ int retval;
+ int has_full_path = 0;
+ int has_zero_pad = 0;
+ int has_bad_modes = 0;
+ int has_dup_entries = 0;
+ int not_properly_sorted = 0;
+ struct tree_entry_list *entry, *last;
+
+ last = NULL;
+ for (entry = item->entries; entry; entry = entry->next) {
+ if (strchr(entry->name, '/'))
+ has_full_path = 1;
+ has_zero_pad |= entry->zeropad;
+
+ switch (entry->mode) {
+ /*
+ * Standard modes..
+ */
+ case S_IFREG | 0755:
+ case S_IFREG | 0644:
+ case S_IFLNK:
+ case S_IFDIR:
+ break;
+ /*
+ * This is nonstandard, but we had a few of these
+ * early on when we honored the full set of mode
+ * bits..
+ */
+ case S_IFREG | 0664:
+ if (!check_strict)
+ break;
+ default:
+ has_bad_modes = 1;
+ }
+
+ if (last) {
+ switch (verify_ordered(last, entry)) {
+ case TREE_UNORDERED:
+ not_properly_sorted = 1;
+ break;
+ case TREE_HAS_DUPS:
+ has_dup_entries = 1;
+ break;
+ default:
+ break;
+ }
+ free(last->name);
+ free(last);
+ }
+
+ last = entry;
+ }
+ if (last) {
+ free(last->name);
+ free(last);
+ }
+ item->entries = NULL;
+
+ retval = 0;
+ if (has_full_path) {
+ objwarning(&item->object, "contains full pathnames");
+ }
+ if (has_zero_pad) {
+ objwarning(&item->object, "contains zero-padded file modes");
+ }
+ if (has_bad_modes) {
+ objwarning(&item->object, "contains bad file modes");
+ }
+ if (has_dup_entries) {
+ retval = objerror(&item->object, "contains duplicate file entries");
+ }
+ if (not_properly_sorted) {
+ retval = objerror(&item->object, "not properly sorted");
+ }
+ return retval;
+}
+
+static int fsck_commit(struct commit *commit)
+{
+ char *buffer = commit->buffer;
+ unsigned char tree_sha1[20], sha1[20];
+
+ if (memcmp(buffer, "tree ", 5))
+ return objerror(&commit->object, "invalid format - expected 'tree' line");
+ if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
+ return objerror(&commit->object, "invalid 'tree' line format - bad sha1");
+ buffer += 46;
+ while (!memcmp(buffer, "parent ", 7)) {
+ if (get_sha1_hex(buffer+7, sha1) || buffer[47] != '\n')
+ return objerror(&commit->object, "invalid 'parent' line format - bad sha1");
+ buffer += 48;
+ }
+ if (memcmp(buffer, "author ", 7))
+ return objerror(&commit->object, "invalid format - expected 'author' line");
+ free(commit->buffer);
+ commit->buffer = NULL;
+ if (!commit->tree)
+ return objerror(&commit->object, "could not load commit's tree %s", tree_sha1);
+ if (!commit->parents && show_root)
+ printf("root %s\n", sha1_to_hex(commit->object.sha1));
+ if (!commit->date)
+ printf("bad commit date in %s\n",
+ sha1_to_hex(commit->object.sha1));
+ return 0;
+}
+
+static int fsck_tag(struct tag *tag)
+{
+ struct object *tagged = tag->tagged;
+
+ if (!tagged) {
+ return objerror(&tag->object, "could not load tagged object");
+ }
+ if (!show_tags)
+ return 0;
+
+ printf("tagged %s %s", tagged->type, sha1_to_hex(tagged->sha1));
+ printf(" (%s) in %s\n", tag->tag, sha1_to_hex(tag->object.sha1));
+ return 0;
+}
+
+static int fsck_sha1(unsigned char *sha1)
+{
+ struct object *obj = parse_object(sha1);
+ if (!obj)
+ return error("%s: object not found", sha1_to_hex(sha1));
+ if (obj->type == blob_type)
+ return 0;
+ if (obj->type == tree_type)
+ return fsck_tree((struct tree *) obj);
+ if (obj->type == commit_type)
+ return fsck_commit((struct commit *) obj);
+ if (obj->type == tag_type)
+ return fsck_tag((struct tag *) obj);
+ /* By now, parse_object() would've returned NULL instead. */
+ return objerror(obj, "unknown type '%s' (internal fsck error)", obj->type);
+}
+
+/*
+ * This is the sorting chunk size: make it reasonably
+ * big so that we can sort well..
+ */
+#define MAX_SHA1_ENTRIES (1024)
+
+struct sha1_entry {
+ unsigned long ino;
+ unsigned char sha1[20];
+};
+
+static struct {
+ unsigned long nr;
+ struct sha1_entry *entry[MAX_SHA1_ENTRIES];
+} sha1_list;
+
+static int ino_compare(const void *_a, const void *_b)
+{
+ const struct sha1_entry *a = _a, *b = _b;
+ unsigned long ino1 = a->ino, ino2 = b->ino;
+ return ino1 < ino2 ? -1 : ino1 > ino2 ? 1 : 0;
+}
+
+static void fsck_sha1_list(void)
+{
+ int i, nr = sha1_list.nr;
+
+ qsort(sha1_list.entry, nr, sizeof(struct sha1_entry *), ino_compare);
+ for (i = 0; i < nr; i++) {
+ struct sha1_entry *entry = sha1_list.entry[i];
+ unsigned char *sha1 = entry->sha1;
+
+ sha1_list.entry[i] = NULL;
+ fsck_sha1(sha1);
+ free(entry);
+ }
+ sha1_list.nr = 0;
+}
+
+static void add_sha1_list(unsigned char *sha1, unsigned long ino)
+{
+ struct sha1_entry *entry = xmalloc(sizeof(*entry));
+ int nr;
+
+ entry->ino = ino;
+ memcpy(entry->sha1, sha1, 20);
+ nr = sha1_list.nr;
+ if (nr == MAX_SHA1_ENTRIES) {
+ fsck_sha1_list();
+ nr = 0;
+ }
+ sha1_list.entry[nr] = entry;
+ sha1_list.nr = ++nr;
+}
+
+static int fsck_dir(int i, char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *de;
+
+ if (!dir)
+ return 0;
+
+ while ((de = readdir(dir)) != NULL) {
+ char name[100];
+ unsigned char sha1[20];
+ int len = strlen(de->d_name);
+
+ switch (len) {
+ case 2:
+ if (de->d_name[1] != '.')
+ break;
+ case 1:
+ if (de->d_name[0] != '.')
+ break;
+ continue;
+ case 38:
+ sprintf(name, "%02x", i);
+ memcpy(name+2, de->d_name, len+1);
+ if (get_sha1_hex(name, sha1) < 0)
+ break;
+ add_sha1_list(sha1, de->d_ino);
+ continue;
+ }
+ fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
+ }
+ closedir(dir);
+ return 0;
+}
+
+static int default_refs = 0;
+
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
+{
+ struct object *obj;
+
+ obj = lookup_object(sha1);
+ if (!obj) {
+ if (!standalone && has_sha1_file(sha1)) {
+ default_refs++;
+ return 0; /* it is in a pack */
+ }
+ error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+ /* We'll continue with the rest despite the error.. */
+ return 0;
+ }
+ default_refs++;
+ obj->used = 1;
+ mark_reachable(obj, REACHABLE);
+ return 0;
+}
+
+static void get_default_heads(void)
+{
+ for_each_ref(fsck_handle_ref);
+ if (!default_refs)
+ die("No default references");
+}
+
+static void fsck_object_dir(const char *path)
+{
+ int i;
+ for (i = 0; i < 256; i++) {
+ static char dir[4096];
+ sprintf(dir, "%s/%02x", path, i);
+ fsck_dir(i, dir);
+ }
+ fsck_sha1_list();
+}
+
+static int fsck_head_link(void)
+{
+ unsigned char sha1[20];
+ const char *git_HEAD = strdup(git_path("HEAD"));
+ const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 1);
+ int pfxlen = strlen(git_HEAD) - 4; /* strip .../.git/ part */
+
+ if (!git_refs_heads_master)
+ return error("HEAD is not a symbolic ref");
+ if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11))
+ return error("HEAD points to something strange (%s)",
+ git_refs_heads_master + pfxlen);
+ if (!memcmp(null_sha1, sha1, 20))
+ return error("HEAD: not a valid git pointer");
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int i, heads;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (!strcmp(arg, "--unreachable")) {
+ show_unreachable = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--tags")) {
+ show_tags = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--root")) {
+ show_root = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--cache")) {
+ keep_cache_objects = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--standalone")) {
+ standalone = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--full")) {
+ check_full = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--strict")) {
+ check_strict = 1;
+ continue;
+ }
+ if (*arg == '-')
+ usage("git-fsck-objects [--tags] [--root] [[--unreachable] [--cache] [--standalone | --full] [--strict] <head-sha1>*]");
+ }
+
+ if (standalone && check_full)
+ die("Only one of --standalone or --full can be used.");
+ if (standalone)
+ putenv("GIT_ALTERNATE_OBJECT_DIRECTORIES=");
+
+ fsck_head_link();
+ fsck_object_dir(get_object_directory());
+ if (check_full) {
+ struct alternate_object_database *alt;
+ struct packed_git *p;
+ prepare_alt_odb();
+ for (alt = alt_odb_list; alt; alt = alt->next) {
+ char namebuf[PATH_MAX];
+ int namelen = alt->name - alt->base;
+ memcpy(namebuf, alt->base, namelen);
+ namebuf[namelen - 1] = 0;
+ fsck_object_dir(namebuf);
+ }
+ prepare_packed_git();
+ for (p = packed_git; p; p = p->next)
+ /* verify gives error messages itself */
+ verify_pack(p, 0);
+
+ for (p = packed_git; p; p = p->next) {
+ int num = num_packed_objects(p);
+ for (i = 0; i < num; i++) {
+ unsigned char sha1[20];
+ nth_packed_object_sha1(p, i, sha1);
+ fsck_sha1(sha1);
+ }
+ }
+ }
+
+ heads = 0;
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-')
+ continue;
+
+ if (!get_sha1(arg, head_sha1)) {
+ struct object *obj = lookup_object(head_sha1);
+
+ /* Error is printed by lookup_object(). */
+ if (!obj)
+ continue;
+
+ obj->used = 1;
+ mark_reachable(obj, REACHABLE);
+ heads++;
+ continue;
+ }
+ error("invalid parameter: expected sha1, got '%s'", arg);
+ }
+
+ /*
+ * If we've not been given any explicit head information, do the
+ * default ones from .git/refs. We also consider the index file
+ * in this case (ie this implies --cache).
+ */
+ if (!heads) {
+ get_default_heads();
+ keep_cache_objects = 1;
+ }
+
+ if (keep_cache_objects) {
+ int i;
+ read_cache();
+ for (i = 0; i < active_nr; i++) {
+ struct blob *blob = lookup_blob(active_cache[i]->sha1);
+ struct object *obj;
+ if (!blob)
+ continue;
+ obj = &blob->object;
+ obj->used = 1;
+ mark_reachable(obj, REACHABLE);
+ }
+ }
+
+ check_connectivity();
+ return 0;
+}
diff --git a/get-tar-commit-id.c b/get-tar-commit-id.c
new file mode 100644
index 000000000..416629035
--- /dev/null
+++ b/get-tar-commit-id.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2005 Rene Scharfe
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define HEADERSIZE 1024
+
+int main(int argc, char **argv)
+{
+ char buffer[HEADERSIZE];
+ ssize_t n;
+
+ n = read(0, buffer, HEADERSIZE);
+ if (n < HEADERSIZE) {
+ fprintf(stderr, "read error\n");
+ return 3;
+ }
+ if (buffer[156] != 'g')
+ return 1;
+ if (memcmp(&buffer[512], "52 comment=", 11))
+ return 1;
+ n = write(1, &buffer[523], 41);
+ if (n < 41) {
+ fprintf(stderr, "write error\n");
+ return 2;
+ }
+ return 0;
+}
diff --git a/git-add.sh b/git-add.sh
new file mode 100755
index 000000000..b5fe46aa2
--- /dev/null
+++ b/git-add.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+usage() {
+ die "usage: git add [-n] [-v] <file>..."
+}
+
+show_only=
+verbose=
+while : ; do
+ case "$1" in
+ -n)
+ show_only=true
+ ;;
+ -v)
+ verbose=--verbose
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+GIT_DIR=$(git-rev-parse --git-dir) || exit
+
+if test -f "$GIT_DIR/info/exclude"
+then
+ git-ls-files -z \
+ --exclude-from="$GIT_DIR/info/exclude" \
+ --others --exclude-per-directory=.gitignore -- "$@"
+else
+ git-ls-files -z \
+ --others --exclude-per-directory=.gitignore -- "$@"
+fi |
+case "$show_only" in
+true)
+ xargs -0 echo ;;
+*)
+ git-update-index --add $verbose -z --stdin ;;
+esac
diff --git a/git-am.sh b/git-am.sh
new file mode 100755
index 000000000..8f073c90f
--- /dev/null
+++ b/git-am.sh
@@ -0,0 +1,407 @@
+#!/bin/sh
+#
+#
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ echo >&2 "usage: $0 [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>"
+ echo >&2 " or, when resuming"
+ echo >&2 " $0 [--skip | --resolved]"
+ exit 1;
+}
+
+stop_here () {
+ echo "$1" >"$dotest/next"
+ exit 1
+}
+
+go_next () {
+ rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
+ "$dotest/patch" "$dotest/info"
+ echo "$next" >"$dotest/next"
+ this=$next
+}
+
+fall_back_3way () {
+ O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+
+ rm -fr "$dotest"/patch-merge-*
+ mkdir "$dotest/patch-merge-tmp-dir"
+
+ # First see if the patch records the index info that we can use.
+ if git-apply -z --index-info "$dotest/patch" \
+ >"$dotest/patch-merge-index-info" 2>/dev/null &&
+ GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+ git-update-index -z --index-info <"$dotest/patch-merge-index-info" &&
+ GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
+ git-write-tree >"$dotest/patch-merge-base+" &&
+ # index has the base tree now.
+ (
+ cd "$dotest/patch-merge-tmp-dir" &&
+ GIT_INDEX_FILE="../patch-merge-tmp-index" \
+ GIT_OBJECT_DIRECTORY="$O_OBJECT" \
+ git-apply $binary --index <../patch
+ )
+ then
+ echo Using index info to reconstruct a base tree...
+ mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
+ mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
+ else
+ # Otherwise, try nearby trees that can be used to apply the
+ # patch.
+ (
+ N=10
+
+ # Hoping the patch is against our recent commits...
+ git-rev-list --max-count=$N HEAD
+
+ # or hoping the patch is against known tags...
+ git-ls-remote --tags .
+ ) |
+ while read base junk
+ do
+ # See if we have it as a tree...
+ git-cat-file tree "$base" >/dev/null 2>&1 || continue
+
+ rm -fr "$dotest"/patch-merge-* &&
+ mkdir "$dotest/patch-merge-tmp-dir" || break
+ (
+ cd "$dotest/patch-merge-tmp-dir" &&
+ GIT_INDEX_FILE=../patch-merge-tmp-index &&
+ GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
+ export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
+ git-read-tree "$base" &&
+ git-apply $binary --index &&
+ mv ../patch-merge-tmp-index ../patch-merge-index &&
+ echo "$base" >../patch-merge-base
+ ) <"$dotest/patch" 2>/dev/null && break
+ done
+ fi
+
+ test -f "$dotest/patch-merge-index" &&
+ his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
+ orig_tree=$(cat "$dotest/patch-merge-base") &&
+ rm -fr "$dotest"/patch-merge-* || exit 1
+
+ echo Falling back to patching base and 3-way merge...
+
+ # This is not so wrong. Depending on which base we picked,
+ # orig_tree may be wildly different from ours, but his_tree
+ # has the same set of wildly different changes in parts the
+ # patch did not touch, so resolve ends up cancelling them,
+ # saying that we reverted all those changes.
+
+ git-merge-resolve $orig_tree -- HEAD $his_tree || {
+ echo Failed to merge in the changes.
+ exit 1
+ }
+}
+
+prec=4
+dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary=
+
+while case "$#" in 0) break;; esac
+do
+ case "$1" in
+ -d=*|--d=*|--do=*|--dot=*|--dote=*|--dotes=*|--dotest=*)
+ dotest=`expr "$1" : '-[^=]*=\(.*\)'`; shift ;;
+ -d|--d|--do|--dot|--dote|--dotes|--dotest)
+ case "$#" in 1) usage ;; esac; shift
+ dotest="$1"; shift;;
+
+ -i|--i|--in|--int|--inte|--inter|--intera|--interac|--interact|\
+ --interacti|--interactiv|--interactive)
+ interactive=t; shift ;;
+
+ -b|--b|--bi|--bin|--bina|--binar|--binary)
+ binary=t; shift ;;
+
+ -3|--3|--3w|--3wa|--3way)
+ threeway=t; shift ;;
+ -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+ sign=t; shift ;;
+ -u|--u|--ut|--utf|--utf8)
+ utf8=t; shift ;;
+ -k|--k|--ke|--kee|--keep)
+ keep=t; shift ;;
+
+ -r|--r|--re|--res|--reso|--resol|--resolv|--resolve|--resolved)
+ resolved=t; shift ;;
+
+ --sk|--ski|--skip)
+ skip=t; shift ;;
+
+ --)
+ shift; break ;;
+ -*)
+ usage ;;
+ *)
+ break ;;
+ esac
+done
+
+# If the dotest directory exists, but we have finished applying all the
+# patches in them, clear it out.
+if test -d "$dotest" &&
+ last=$(cat "$dotest/last") &&
+ next=$(cat "$dotest/next") &&
+ test $# != 0 &&
+ test "$next" -gt "$last"
+then
+ rm -fr "$dotest"
+fi
+
+if test -d "$dotest"
+then
+ test ",$#," = ",0," ||
+ die "previous dotest directory $dotest still exists but mbox given."
+ resume=yes
+else
+ # Make sure we are not given --skip nor --resolved
+ test ",$skip,$resolved," = ,,, ||
+ die "we are not resuming."
+
+ # Start afresh.
+ mkdir -p "$dotest" || exit
+
+ # cat does the right thing for us, including '-' to mean
+ # standard input.
+ cat "$@" |
+ git-mailsplit -d$prec "$dotest/" >"$dotest/last" || {
+ rm -fr "$dotest"
+ exit 1
+ }
+
+ # -b, -s, -u and -k flags are kept for the resuming session after
+ # a patch failure.
+ # -3 and -i can and must be given when resuming.
+ echo "$binary" >"$dotest/binary"
+ echo "$sign" >"$dotest/sign"
+ echo "$utf8" >"$dotest/utf8"
+ echo "$keep" >"$dotest/keep"
+ echo 1 >"$dotest/next"
+fi
+
+case "$resolved" in
+'')
+ files=$(git-diff-index --cached --name-only HEAD) || exit
+ if [ "$files" ]; then
+ echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+ exit 1
+ fi
+esac
+
+if test "$(cat "$dotest/binary")" = t
+then
+ binary=--allow-binary-replacement
+fi
+if test "$(cat "$dotest/utf8")" = t
+then
+ utf8=-u
+fi
+if test "$(cat "$dotest/keep")" = t
+then
+ keep=-k
+fi
+if test "$(cat "$dotest/sign")" = t
+then
+ SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
+ s/>.*/>/
+ s/^/Signed-off-by: /'
+ `
+else
+ SIGNOFF=
+fi
+
+last=`cat "$dotest/last"`
+this=`cat "$dotest/next"`
+if test "$skip" = t
+then
+ this=`expr "$this" + 1`
+fi
+
+if test "$this" -gt "$last"
+then
+ echo Nothing to do.
+ rm -fr "$dotest"
+ exit
+fi
+
+while test "$this" -le "$last"
+do
+ msgnum=`printf "%0${prec}d" $this`
+ next=`expr "$this" + 1`
+ test -f "$dotest/$msgnum" || {
+ go_next
+ continue
+ }
+
+ # If we are not resuming, parse and extract the patch information
+ # into separate files:
+ # - info records the authorship and title
+ # - msg is the rest of commit log message
+ # - 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.
+ case "$resume" in
+ '')
+ git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
+ <"$dotest/$msgnum" >"$dotest/info" ||
+ stop_here $this
+ git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
+ ;;
+ esac
+
+ GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$dotest/info")"
+ GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
+ GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
+ export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+
+ SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
+ case "$keep_subject" in -k) SUBJECT="[PATCH] $SUBJECT" ;; esac
+
+ case "$resume" in
+ '')
+ if test '' != "$SIGNOFF"
+ then
+ LAST_SIGNED_OFF_BY=`
+ sed -ne '/^Signed-off-by: /p' \
+ "$dotest/msg-clean" |
+ tail -n 1
+ `
+ ADD_SIGNOFF=`
+ test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+ test '' = "$LAST_SIGNED_OFF_BY" && echo
+ echo "$SIGNOFF"
+ }`
+ else
+ ADD_SIGNOFF=
+ fi
+ {
+ echo "$SUBJECT"
+ if test -s "$dotest/msg-clean"
+ then
+ echo
+ cat "$dotest/msg-clean"
+ fi
+ if test '' != "$ADD_SIGNOFF"
+ then
+ echo "$ADD_SIGNOFF"
+ fi
+ } >"$dotest/final-commit"
+ ;;
+ *)
+ case "$resolved,$interactive" in
+ tt)
+ # This is used only for interactive view option.
+ git-diff-index -p --cached HEAD >"$dotest/patch"
+ ;;
+ esac
+ esac
+
+ resume=
+ if test "$interactive" = t
+ then
+ test -t 0 ||
+ die "cannot be interactive without stdin connected to a terminal."
+ action=again
+ while test "$action" = again
+ do
+ echo "Commit Body is:"
+ echo "--------------------------"
+ cat "$dotest/final-commit"
+ echo "--------------------------"
+ echo -n "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all "
+ read reply
+ case "$reply" in
+ [yY]*) action=yes ;;
+ [aA]*) action=yes interactive= ;;
+ [nN]*) action=skip ;;
+ [eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
+ action=again ;;
+ [vV]*) action=again
+ LESS=-S ${PAGER:-less} "$dotest/patch" ;;
+ *) action=again ;;
+ esac
+ done
+ else
+ action=yes
+ fi
+
+ if test $action = skip
+ then
+ go_next
+ continue
+ fi
+
+ if test -x "$GIT_DIR"/hooks/applypatch-msg
+ then
+ "$GIT_DIR"/hooks/applypatch-msg "$dotest/final-commit" ||
+ stop_here $this
+ fi
+
+ echo
+ echo "Applying '$SUBJECT'"
+ echo
+
+ case "$resolved" in
+ '')
+ git-apply $binary --index "$dotest/patch"
+ apply_status=$?
+ ;;
+ t)
+ # Resolved means the user did all the hard work, and
+ # we do not have to do any patch application. Just
+ # trust what the user has in the index file and the
+ # working tree.
+ resolved=
+ apply_status=0
+ ;;
+ esac
+
+ if test $apply_status = 1 && test "$threeway" = t
+ then
+ if (fall_back_3way)
+ then
+ # Applying the patch to an earlier tree and merging the
+ # result may have produced the same tree as ours.
+ changed="$(git-diff-index --cached --name-only -z HEAD)"
+ if test '' = "$changed"
+ then
+ echo No changes -- Patch already applied.
+ go_next
+ continue
+ fi
+ # clear apply_status -- we have successfully merged.
+ apply_status=0
+ fi
+ fi
+ if test $apply_status != 0
+ then
+ echo Patch failed at $msgnum.
+ stop_here $this
+ fi
+
+ if test -x "$GIT_DIR"/hooks/pre-applypatch
+ then
+ "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
+ fi
+
+ tree=$(git-write-tree) &&
+ echo Wrote tree $tree &&
+ parent=$(git-rev-parse --verify HEAD) &&
+ commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
+ echo Committed: $commit &&
+ git-update-ref HEAD $commit $parent ||
+ stop_here $this
+
+ if test -x "$GIT_DIR"/hooks/post-applypatch
+ then
+ "$GIT_DIR"/hooks/post-applypatch
+ fi
+
+ go_next
+done
+
+rm -fr "$dotest"
diff --git a/git-applymbox.sh b/git-applymbox.sh
new file mode 100755
index 000000000..6de693287
--- /dev/null
+++ b/git-applymbox.sh
@@ -0,0 +1,118 @@
+#!/bin/sh
+##
+## "dotest" is my stupid name for my patch-application script, which
+## I never got around to renaming after I tested it. We're now on the
+## second generation of scripts, still called "dotest".
+##
+## Update: Ryan Anderson finally shamed me into naming this "applymbox".
+##
+## You give it a mbox-format collection of emails, and it will try to
+## apply them to the kernel using "applypatch"
+##
+## The patch application may fail in the middle. In which case:
+## (1) look at .dotest/patch and fix it up to apply
+## (2) re-run applymbox with -c .dotest/msg-number for the current one.
+## Pay a special attention to the commit log message if you do this and
+## use a Signoff_file, because applypatch wants to append the sign-off
+## message to msg-clean every time it is run.
+##
+## git-am is supposed to be the newer and better tool for this job.
+
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ echo >&2 "applymbox [-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]"
+ exit 1
+}
+
+keep_subject= query_apply= continue= utf8= resume=t
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -u) utf8=-u ;;
+ -k) keep_subject=-k ;;
+ -q) query_apply=t ;;
+ -c) continue="$2"; resume=f; shift ;;
+ -m) fallback_3way=t ;;
+ -*) usage ;;
+ *) break ;;
+ esac
+ shift
+done
+
+case "$continue" in
+'')
+ rm -rf .dotest
+ mkdir .dotest
+ num_msgs=$(git-mailsplit "$1" .dotest) || exit 1
+ echo "$num_msgs patch(es) to process."
+ shift
+esac
+
+files=$(git-diff-index --cached --name-only HEAD) || exit
+if [ "$files" ]; then
+ echo "Dirty index: cannot apply patches (dirty: $files)" >&2
+ exit 1
+fi
+
+case "$query_apply" in
+t) touch .dotest/.query_apply
+esac
+case "$fall_back_3way" in
+t) : >.dotest/.3way
+esac
+case "$keep_subject" in
+-k) : >.dotest/.keep_subject
+esac
+
+signoff="$1"
+set x .dotest/0*
+shift
+while case "$#" in 0) break;; esac
+do
+ i="$1"
+ case "$resume,$continue" in
+ f,$i) resume=t;;
+ f,*) shift
+ continue;;
+ *)
+ git-mailinfo $keep_subject $utf8 \
+ .dotest/msg .dotest/patch <$i >.dotest/info || exit 1
+ git-stripspace < .dotest/msg > .dotest/msg-clean
+ ;;
+ esac
+ while :; # for fixing up and retry
+ do
+ git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff"
+ case "$?" in
+ 0)
+ # Remove the cleanly applied one to reduce clutter.
+ rm -f .dotest/$i
+ ;;
+ 2)
+ # 2 is a special exit code from applypatch to indicate that
+ # the patch wasn't applied, but continue anyway
+ ;;
+ *)
+ ret=$?
+ if test -f .dotest/.query_apply
+ then
+ echo >&2 "* Patch failed."
+ echo >&2 "* You could fix it up in your editor and"
+ echo >&2 " retry. If you want to do so, say yes here"
+ echo >&2 " AFTER fixing .dotest/patch up."
+ echo >&2 -n "Retry [y/N]? "
+ read yesno
+ case "$yesno" in
+ [Yy]*)
+ continue ;;
+ esac
+ fi
+ exit $ret
+ esac
+ break
+ done
+ shift
+done
+# return to pristine
+rm -fr .dotest
diff --git a/git-applypatch.sh b/git-applypatch.sh
new file mode 100755
index 000000000..66fd19ae2
--- /dev/null
+++ b/git-applypatch.sh
@@ -0,0 +1,197 @@
+#!/bin/sh
+##
+## applypatch takes four file arguments, and uses those to
+## apply the unpacked patch (surprise surprise) that they
+## represent to the current tree.
+##
+## The arguments are:
+## $1 - file with commit message
+## $2 - file with the actual patch
+## $3 - "info" file with Author, email and subject
+## $4 - optional file containing signoff to add
+##
+. git-sh-setup || die "Not a git archive."
+
+final=.dotest/final-commit
+##
+## If this file exists, we ask before applying
+##
+query_apply=.dotest/.query_apply
+
+## We do not munge the first line of the commit message too much
+## if this file exists.
+keep_subject=.dotest/.keep_subject
+
+## We do not attempt the 3-way merge fallback unless this file exists.
+fall_back_3way=.dotest/.3way
+
+MSGFILE=$1
+PATCHFILE=$2
+INFO=$3
+SIGNOFF=$4
+EDIT=${VISUAL:-${EDITOR:-vi}}
+
+export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")"
+export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")"
+export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")"
+export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")"
+
+if test '' != "$SIGNOFF"
+then
+ if test -f "$SIGNOFF"
+ then
+ SIGNOFF=`cat "$SIGNOFF"` || exit
+ elif case "$SIGNOFF" in yes | true | me | please) : ;; *) false ;; esac
+ then
+ SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
+ s/>.*/>/
+ s/^/Signed-off-by: /'
+ `
+ else
+ SIGNOFF=
+ fi
+ if test '' != "$SIGNOFF"
+ then
+ LAST_SIGNED_OFF_BY=`
+ sed -ne '/^Signed-off-by: /p' "$MSGFILE" |
+ tail -n 1
+ `
+ test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
+ test '' = "$LAST_SIGNED_OFF_BY" && echo
+ echo "$SIGNOFF"
+ } >>"$MSGFILE"
+ fi
+fi
+
+patch_header=
+test -f "$keep_subject" || patch_header='[PATCH] '
+
+{
+ echo "$patch_header$SUBJECT"
+ if test -s "$MSGFILE"
+ then
+ echo
+ cat "$MSGFILE"
+ fi
+} >"$final"
+
+interactive=yes
+test -f "$query_apply" || interactive=no
+
+while [ "$interactive" = yes ]; do
+ echo "Commit Body is:"
+ echo "--------------------------"
+ cat "$final"
+ echo "--------------------------"
+ echo -n "Apply? [y]es/[n]o/[e]dit/[a]ccept all "
+ read reply
+ case "$reply" in
+ y|Y) interactive=no;;
+ n|N) exit 2;; # special value to tell dotest to keep going
+ e|E) "$EDIT" "$final";;
+ a|A) rm -f "$query_apply"
+ interactive=no ;;
+ esac
+done
+
+if test -x "$GIT_DIR"/hooks/applypatch-msg
+then
+ "$GIT_DIR"/hooks/applypatch-msg "$final" || exit
+fi
+
+echo
+echo Applying "'$SUBJECT'"
+echo
+
+git-apply --index "$PATCHFILE" || {
+
+ # git-apply exits with status 1 when the patch does not apply,
+ # but it die()s with other failures, most notably upon corrupt
+ # patch. In the latter case, there is no point to try applying
+ # it to another tree and do 3-way merge.
+ test $? = 1 || exit 1
+
+ test -f "$fall_back_3way" || exit 1
+
+ # Here if we know which revision the patch applies to,
+ # we create a temporary working tree and index, apply the
+ # patch, and attempt 3-way merge with the resulting tree.
+
+ O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
+ rm -fr .patch-merge-*
+
+ (
+ N=10
+
+ # if the patch records the base tree...
+ sed -ne '
+ /^diff /q
+ /^applies-to: \([0-9a-f]*\)$/{
+ s//\1/p
+ q
+ }
+ ' "$PATCHFILE"
+
+ # or hoping the patch is against our recent commits...
+ git-rev-list --max-count=$N HEAD
+
+ # or hoping the patch is against known tags...
+ git-ls-remote --tags .
+ ) |
+ while read base junk
+ do
+ # Try it if we have it as a tree.
+ git-cat-file tree "$base" >/dev/null 2>&1 || continue
+
+ rm -fr .patch-merge-tmp-* &&
+ mkdir .patch-merge-tmp-dir || break
+ (
+ cd .patch-merge-tmp-dir &&
+ GIT_INDEX_FILE=../.patch-merge-tmp-index &&
+ GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
+ export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
+ git-read-tree "$base" &&
+ git-apply --index &&
+ mv ../.patch-merge-tmp-index ../.patch-merge-index &&
+ echo "$base" >../.patch-merge-base
+ ) <"$PATCHFILE" 2>/dev/null && break
+ done
+
+ test -f .patch-merge-index &&
+ his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) &&
+ orig_tree=$(cat .patch-merge-base) &&
+ rm -fr .patch-merge-* || exit 1
+
+ echo Falling back to patching base and 3-way merge using $orig_tree...
+
+ # This is not so wrong. Depending on which base we picked,
+ # orig_tree may be wildly different from ours, but his_tree
+ # has the same set of wildly different changes in parts the
+ # patch did not touch, so resolve ends up cancelling them,
+ # saying that we reverted all those changes.
+
+ if git-merge-resolve $orig_tree -- HEAD $his_tree
+ then
+ echo Done.
+ else
+ echo Failed to merge in the changes.
+ exit 1
+ fi
+}
+
+if test -x "$GIT_DIR"/hooks/pre-applypatch
+then
+ "$GIT_DIR"/hooks/pre-applypatch || exit
+fi
+
+tree=$(git-write-tree) || exit 1
+echo Wrote tree $tree
+parent=$(git-rev-parse --verify HEAD) &&
+commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
+echo Committed: $commit
+git-update-ref HEAD $commit $parent || exit
+
+if test -x "$GIT_DIR"/hooks/post-applypatch
+then
+ "$GIT_DIR"/hooks/post-applypatch
+fi
diff --git a/git-archimport.perl b/git-archimport.perl
new file mode 100755
index 000000000..c3bed0808
--- /dev/null
+++ b/git-archimport.perl
@@ -0,0 +1,852 @@
+#!/usr/bin/perl -w
+#
+# This tool is copyright (c) 2005, Martin Langhoff.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to walk the output of tla abrowse,
+# fetch the changesets and apply them.
+#
+
+=head1 Invocation
+
+ git-archimport [ -h ] [ -v ] [ -T ] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ]
+
+Imports a project from one or more Arch repositories. It will follow branches
+and repositories within the namespaces defined by the <archive/branch>
+parameters suppplied. If it cannot find the remote branch a merge comes from
+it will just import it as a regular commit. If it can find it, it will mark it
+as a merge whenever possible.
+
+See man (1) git-archimport for more details.
+
+=head1 TODO
+
+ - create tag objects instead of ref tags
+ - audit shell-escaping of filenames
+ - hide our private tags somewhere smarter
+ - find a way to make "cat *patches | patch" safe even when patchfiles are missing newlines
+
+=head1 Devel tricks
+
+Add print in front of the shell commands invoked via backticks.
+
+=head1 Devel Notes
+
+There are several places where Arch and git terminology are intermixed
+and potentially confused.
+
+The notion of a "branch" in git is approximately equivalent to
+a "archive/category--branch--version" in Arch. Also, it should be noted
+that the "--branch" portion of "archive/category--branch--version" is really
+optional in Arch although not many people (nor tools!) seem to know this.
+This means that "archive/category--version" is also a valid "branch"
+in git terms.
+
+We always refer to Arch names by their fully qualified variant (which
+means the "archive" name is prefixed.
+
+For people unfamiliar with Arch, an "archive" is the term for "repository",
+and can contain multiple, unrelated branches.
+
+=cut
+
+use strict;
+use warnings;
+use Getopt::Std;
+use File::Spec;
+use File::Temp qw(tempfile tempdir);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use String::ShellQuote;
+use Time::Local;
+use IO::Socket;
+use IO::Pipe;
+use POSIX qw(strftime dup2);
+use Data::Dumper qw/ Dumper /;
+use IPC::Open2;
+
+$SIG{'PIPE'}="IGNORE";
+$ENV{'TZ'}="UTC";
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$ENV{"GIT_DIR"} = $git_dir;
+my $ptag_dir = "$git_dir/archimport/tags";
+
+our($opt_h,$opt_v, $opt_T,$opt_t,$opt_o);
+
+sub usage() {
+ print STDERR <<END;
+Usage: ${\basename $0} # fetch/update GIT from Arch
+ [ -o ] [ -h ] [ -v ] [ -T ] [ -t tempdir ]
+ repository/arch-branch [ repository/arch-branch] ...
+END
+ exit(1);
+}
+
+getopts("Thvt:") or usage();
+usage if $opt_h;
+
+@ARGV >= 1 or usage();
+my @arch_roots = @ARGV;
+
+my ($tmpdir, $tmpdirname) = tempdir('git-archimport-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+my $tmp = $opt_t || 1;
+$tmp = tempdir('git-archimport-XXXXXX', TMPDIR => 1, CLEANUP => 1);
+$opt_v && print "+ Using $tmp as temporary directory\n";
+
+my @psets = (); # the collection
+my %psets = (); # the collection, by name
+
+my %rptags = (); # my reverse private tags
+ # to map a SHA1 to a commitid
+
+foreach my $root (@arch_roots) {
+ my ($arepo, $abranch) = split(m!/!, $root);
+ open ABROWSE, "tla abrowse -f -A $arepo --desc --merges $abranch |"
+ or die "Problems with tla abrowse: $!";
+
+ my %ps = (); # the current one
+ my $mode = '';
+ my $lastseen = '';
+
+ while (<ABROWSE>) {
+ chomp;
+
+ # first record padded w 8 spaces
+ if (s/^\s{8}\b//) {
+
+ # store the record we just captured
+ if (%ps) {
+ my %temp = %ps; # break references
+ push (@psets, \%temp);
+ $psets{$temp{id}} = \%temp;
+ %ps = ();
+ }
+
+ my ($id, $type) = split(m/\s{3}/, $_);
+ $ps{id} = $id;
+ $ps{repo} = $arepo;
+
+ # deal with types
+ if ($type =~ m/^\(simple changeset\)/) {
+ $ps{type} = 's';
+ } elsif ($type eq '(initial import)') {
+ $ps{type} = 'i';
+ } elsif ($type =~ m/^\(tag revision of (.+)\)/) {
+ $ps{type} = 't';
+ $ps{tag} = $1;
+ } else {
+ warn "Unknown type $type";
+ }
+ $lastseen = 'id';
+ }
+
+ if (s/^\s{10}//) {
+ # 10 leading spaces or more
+ # indicate commit metadata
+
+ # date & author
+ if ($lastseen eq 'id' && m/^\d{4}-\d{2}-\d{2}/) {
+
+ my ($date, $authoremail) = split(m/\s{2,}/, $_);
+ $ps{date} = $date;
+ $ps{date} =~ s/\bGMT$//; # strip off trailign GMT
+ if ($ps{date} =~ m/\b\w+$/) {
+ warn 'Arch dates not in GMT?! - imported dates will be wrong';
+ }
+
+ $authoremail =~ m/^(.+)\s(\S+)$/;
+ $ps{author} = $1;
+ $ps{email} = $2;
+
+ $lastseen = 'date';
+
+ } elsif ($lastseen eq 'date') {
+ # the only hint is position
+ # subject is after date
+ $ps{subj} = $_;
+ $lastseen = 'subj';
+
+ } elsif ($lastseen eq 'subj' && $_ eq 'merges in:') {
+ $ps{merges} = [];
+ $lastseen = 'merges';
+
+ } elsif ($lastseen eq 'merges' && s/^\s{2}//) {
+ push (@{$ps{merges}}, $_);
+ } else {
+ warn 'more metadata after merges!?';
+ }
+
+ }
+ }
+
+ if (%ps) {
+ my %temp = %ps; # break references
+ push (@psets, \%temp);
+ $psets{ $temp{id} } = \%temp;
+ %ps = ();
+ }
+ close ABROWSE;
+} # end foreach $root
+
+## Order patches by time
+@psets = sort {$a->{date}.$b->{id} cmp $b->{date}.$b->{id}} @psets;
+
+#print Dumper \@psets;
+
+##
+## TODO cleanup irrelevant patches
+## and put an initial import
+## or a full tag
+my $import = 0;
+unless (-d $git_dir) { # initial import
+ if ($psets[0]{type} eq 'i' || $psets[0]{type} eq 't') {
+ print "Starting import from $psets[0]{id}\n";
+ `git-init-db`;
+ die $! if $?;
+ $import = 1;
+ } else {
+ die "Need to start from an import or a tag -- cannot use $psets[0]{id}";
+ }
+} else { # progressing an import
+ # load the rptags
+ opendir(DIR, "$git_dir/archimport/tags")
+ || die "can't opendir: $!";
+ while (my $file = readdir(DIR)) {
+ # skip non-interesting-files
+ next unless -f "$ptag_dir/$file";
+
+ # convert first '--' to '/' from old git-archimport to use
+ # as an archivename/c--b--v private tag
+ if ($file !~ m!,!) {
+ my $oldfile = $file;
+ $file =~ s!--!,!;
+ print STDERR "converting old tag $oldfile to $file\n";
+ rename("$ptag_dir/$oldfile", "$ptag_dir/$file") or die $!;
+ }
+ my $sha = ptag($file);
+ chomp $sha;
+ $rptags{$sha} = $file;
+ }
+ closedir DIR;
+}
+
+# process patchsets
+# extract the Arch repository name (Arch "archive" in Arch-speak)
+sub extract_reponame {
+ my $fq_cvbr = shift; # archivename/[[[[category]branch]version]revision]
+ return (split(/\//, $fq_cvbr))[0];
+}
+
+sub extract_versionname {
+ my $name = shift;
+ $name =~ s/--(?:patch|version(?:fix)?|base)-\d+$//;
+ return $name;
+}
+
+# convert a fully-qualified revision or version to a unique dirname:
+# normalperson@yhbt.net-05/mpd--uclinux--1--patch-2
+# becomes: normalperson@yhbt.net-05,mpd--uclinux--1
+#
+# the git notion of a branch is closer to
+# archive/category--branch--version than archive/category--branch, so we
+# use this to convert to git branch names.
+# Also, keep archive names but replace '/' with ',' since it won't require
+# subdirectories, and is safer than swapping '--' which could confuse
+# reverse-mapping when dealing with bastard branches that
+# are just archive/category--version (no --branch)
+sub tree_dirname {
+ my $revision = shift;
+ my $name = extract_versionname($revision);
+ $name =~ s#/#,#;
+ return $name;
+}
+
+# old versions of git-archimport just use the <category--branch> part:
+sub old_style_branchname {
+ my $id = shift;
+ my $ret = safe_pipe_capture($TLA,'parse-package-name','-p',$id);
+ chomp $ret;
+ return $ret;
+}
+
+*git_branchname = $opt_o ? *old_style_branchname : *tree_dirname;
+
+# process patchsets
+foreach my $ps (@psets) {
+ $ps->{branch} = git_branchname($ps->{id});
+
+ #
+ # ensure we have a clean state
+ #
+ if (`git diff-files`) {
+ die "Unclean tree when about to process $ps->{id} " .
+ " - did we fail to commit cleanly before?";
+ }
+ die $! if $?;
+
+ #
+ # skip commits already in repo
+ #
+ if (ptag($ps->{id})) {
+ $opt_v && print " * Skipping already imported: $ps->{id}\n";
+ next;
+ }
+
+ print " * Starting to work on $ps->{id}\n";
+
+ #
+ # create the branch if needed
+ #
+ if ($ps->{type} eq 'i' && !$import) {
+ die "Should not have more than one 'Initial import' per GIT import: $ps->{id}";
+ }
+
+ unless ($import) { # skip for import
+ if ( -e "$git_dir/refs/heads/$ps->{branch}") {
+ # we know about this branch
+ `git checkout $ps->{branch}`;
+ } else {
+ # new branch! we need to verify a few things
+ die "Branch on a non-tag!" unless $ps->{type} eq 't';
+ my $branchpoint = ptag($ps->{tag});
+ die "Tagging from unknown id unsupported: $ps->{tag}"
+ unless $branchpoint;
+
+ # find where we are supposed to branch from
+ `git checkout -b $ps->{branch} $branchpoint`;
+
+ # If we trust Arch with the fact that this is just
+ # a tag, and it does not affect the state of the tree
+ # then we just tag and move on
+ tag($ps->{id}, $branchpoint);
+ ptag($ps->{id}, $branchpoint);
+ print " * Tagged $ps->{id} at $branchpoint\n";
+ next;
+ }
+ die $! if $?;
+ }
+
+ #
+ # Apply the import/changeset/merge into the working tree
+ #
+ if ($ps->{type} eq 'i' || $ps->{type} eq 't') {
+ apply_import($ps) or die $!;
+ $import=0;
+ } elsif ($ps->{type} eq 's') {
+ apply_cset($ps);
+ }
+
+ #
+ # prepare update git's index, based on what arch knows
+ # about the pset, resolve parents, etc
+ #
+ my $tree;
+
+ my $commitlog = `tla cat-archive-log -A $ps->{repo} $ps->{id}`;
+ die "Error in cat-archive-log: $!" if $?;
+
+ # parselog will git-add/rm files
+ # and generally prepare things for the commit
+ # NOTE: parselog will shell-quote filenames!
+ my ($sum, $msg, $add, $del, $mod, $ren) = parselog($commitlog);
+ my $logmessage = "$sum\n$msg";
+
+
+ # imports don't give us good info
+ # on added files. Shame on them
+ if ($ps->{type} eq 'i' || $ps->{type} eq 't') {
+ `find . -type f -print0 | grep -zv '^./$git_dir' | xargs -0 -l100 git-update-index --add`;
+ `git-ls-files --deleted -z | xargs --no-run-if-empty -0 -l100 git-update-index --remove`;
+ }
+
+ if (@$add) {
+ while (@$add) {
+ my @slice = splice(@$add, 0, 100);
+ my $slice = join(' ', @slice);
+ `git-update-index --add $slice`;
+ die "Error in git-update-index --add: $!" if $?;
+ }
+ }
+ if (@$del) {
+ foreach my $file (@$del) {
+ unlink $file or die "Problems deleting $file : $!";
+ }
+ while (@$del) {
+ my @slice = splice(@$del, 0, 100);
+ my $slice = join(' ', @slice);
+ `git-update-index --remove $slice`;
+ die "Error in git-update-index --remove: $!" if $?;
+ }
+ }
+ if (@$ren) { # renamed
+ if (@$ren % 2) {
+ die "Odd number of entries in rename!?";
+ }
+ ;
+ while (@$ren) {
+ my $from = pop @$ren;
+ my $to = pop @$ren;
+
+ unless (-d dirname($to)) {
+ mkpath(dirname($to)); # will die on err
+ }
+ #print "moving $from $to";
+ `mv $from $to`;
+ die "Error renaming $from $to : $!" if $?;
+ `git-update-index --remove $from`;
+ die "Error in git-update-index --remove: $!" if $?;
+ `git-update-index --add $to`;
+ die "Error in git-update-index --add: $!" if $?;
+ }
+
+ }
+ if (@$mod) { # must be _after_ renames
+ while (@$mod) {
+ my @slice = splice(@$mod, 0, 100);
+ my $slice = join(' ', @slice);
+ `git-update-index $slice`;
+ die "Error in git-update-index: $!" if $?;
+ }
+ }
+
+ # warn "errors when running git-update-index! $!";
+ $tree = `git-write-tree`;
+ die "cannot write tree $!" if $?;
+ chomp $tree;
+
+
+ #
+ # Who's your daddy?
+ #
+ my @par;
+ if ( -e "$git_dir/refs/heads/$ps->{branch}") {
+ if (open HEAD, "<$git_dir/refs/heads/$ps->{branch}") {
+ my $p = <HEAD>;
+ close HEAD;
+ chomp $p;
+ push @par, '-p', $p;
+ } else {
+ if ($ps->{type} eq 's') {
+ warn "Could not find the right head for the branch $ps->{branch}";
+ }
+ }
+ }
+
+ if ($ps->{merges}) {
+ push @par, find_parents($ps);
+ }
+ my $par = join (' ', @par);
+
+ #
+ # Commit, tag and clean state
+ #
+ $ENV{TZ} = 'GMT';
+ $ENV{GIT_AUTHOR_NAME} = $ps->{author};
+ $ENV{GIT_AUTHOR_EMAIL} = $ps->{email};
+ $ENV{GIT_AUTHOR_DATE} = $ps->{date};
+ $ENV{GIT_COMMITTER_NAME} = $ps->{author};
+ $ENV{GIT_COMMITTER_EMAIL} = $ps->{email};
+ $ENV{GIT_COMMITTER_DATE} = $ps->{date};
+
+ my ($pid, $commit_rh, $commit_wh);
+ $commit_rh = 'commit_rh';
+ $commit_wh = 'commit_wh';
+
+ $pid = open2(*READER, *WRITER, "git-commit-tree $tree $par")
+ or die $!;
+ print WRITER $logmessage; # write
+ close WRITER;
+ my $commitid = <READER>; # read
+ chomp $commitid;
+ close READER;
+ waitpid $pid,0; # close;
+
+ if (length $commitid != 40) {
+ die "Something went wrong with the commit! $! $commitid";
+ }
+ #
+ # Update the branch
+ #
+ open HEAD, ">$git_dir/refs/heads/$ps->{branch}";
+ print HEAD $commitid;
+ close HEAD;
+ system('git-update-ref', 'HEAD', "$ps->{branch}");
+
+ # tag accordingly
+ ptag($ps->{id}, $commitid); # private tag
+ if ($opt_T || $ps->{type} eq 't' || $ps->{type} eq 'i') {
+ tag($ps->{id}, $commitid);
+ }
+ print " * Committed $ps->{id}\n";
+ print " + tree $tree\n";
+ print " + commit $commitid\n";
+ $opt_v && print " + commit date is $ps->{date} \n";
+ $opt_v && print " + parents: $par \n";
+}
+
+sub apply_import {
+ my $ps = shift;
+ my $bname = git_branchname($ps->{id});
+
+ `mkdir -p $tmp`;
+
+ `tla get -s --no-pristine -A $ps->{repo} $ps->{id} $tmp/import`;
+ die "Cannot get import: $!" if $?;
+ `rsync -v --archive --delete --exclude '$git_dir' --exclude '.arch-ids' --exclude '{arch}' $tmp/import/* ./`;
+ die "Cannot rsync import:$!" if $?;
+
+ `rm -fr $tmp/import`;
+ die "Cannot remove tempdir: $!" if $?;
+
+
+ return 1;
+}
+
+sub apply_cset {
+ my $ps = shift;
+
+ `mkdir -p $tmp`;
+
+ # get the changeset
+ `tla get-changeset -A $ps->{repo} $ps->{id} $tmp/changeset`;
+ die "Cannot get changeset: $!" if $?;
+
+ # apply patches
+ if (`find $tmp/changeset/patches -type f -name '*.patch'`) {
+ # this can be sped up considerably by doing
+ # (find | xargs cat) | patch
+ # but that cna get mucked up by patches
+ # with missing trailing newlines or the standard
+ # 'missing newline' flag in the patch - possibly
+ # produced with an old/buggy diff.
+ # slow and safe, we invoke patch once per patchfile
+ `find $tmp/changeset/patches -type f -name '*.patch' -print0 | grep -zv '{arch}' | xargs -iFILE -0 --no-run-if-empty patch -p1 --forward -iFILE`;
+ die "Problem applying patches! $!" if $?;
+ }
+
+ # apply changed binary files
+ if (my @modified = `find $tmp/changeset/patches -type f -name '*.modified'`) {
+ foreach my $mod (@modified) {
+ chomp $mod;
+ my $orig = $mod;
+ $orig =~ s/\.modified$//; # lazy
+ $orig =~ s!^\Q$tmp\E/changeset/patches/!!;
+ #print "rsync -p '$mod' '$orig'";
+ `rsync -p $mod ./$orig`;
+ die "Problem applying binary changes! $!" if $?;
+ }
+ }
+
+ # bring in new files
+ `rsync --archive --exclude '$git_dir' --exclude '.arch-ids' --exclude '{arch}' $tmp/changeset/new-files-archive/* ./`;
+
+ # deleted files are hinted from the commitlog processing
+
+ `rm -fr $tmp/changeset`;
+}
+
+
+# =for reference
+# A log entry looks like
+# Revision: moodle-org--moodle--1.3.3--patch-15
+# Archive: arch-eduforge@catalyst.net.nz--2004
+# Creator: Penny Leach <penny@catalyst.net.nz>
+# Date: Wed May 25 14:15:34 NZST 2005
+# Standard-date: 2005-05-25 02:15:34 GMT
+# New-files: lang/de/.arch-ids/block_glossary_random.php.id
+# lang/de/.arch-ids/block_html.php.id
+# New-directories: lang/de/help/questionnaire
+# lang/de/help/questionnaire/.arch-ids
+# Renamed-files: .arch-ids/db_sears.sql.id db/.arch-ids/db_sears.sql.id
+# db_sears.sql db/db_sears.sql
+# Removed-files: lang/be/docs/.arch-ids/release.html.id
+# lang/be/docs/.arch-ids/releaseold.html.id
+# Modified-files: admin/cron.php admin/delete.php
+# admin/editor.html backup/lib.php backup/restore.php
+# New-patches: arch-eduforge@catalyst.net.nz--2004/moodle-org--moodle--1.3.3--patch-15
+# Summary: Updating to latest from MOODLE_14_STABLE (1.4.5+)
+# Keywords:
+#
+# Updating yadda tadda tadda madda
+sub parselog {
+ my $log = shift;
+ #print $log;
+
+ my (@add, @del, @mod, @ren, @kw, $sum, $msg );
+
+ if ($log =~ m/(?:\n|^)New-files:(.*?)(?=\n\w)/s ) {
+ my $files = $1;
+ @add = split(m/\s+/s, $files);
+ }
+
+ if ($log =~ m/(?:\n|^)Removed-files:(.*?)(?=\n\w)/s ) {
+ my $files = $1;
+ @del = split(m/\s+/s, $files);
+ }
+
+ if ($log =~ m/(?:\n|^)Modified-files:(.*?)(?=\n\w)/s ) {
+ my $files = $1;
+ @mod = split(m/\s+/s, $files);
+ }
+
+ if ($log =~ m/(?:\n|^)Renamed-files:(.*?)(?=\n\w)/s ) {
+ my $files = $1;
+ @ren = split(m/\s+/s, $files);
+ }
+
+ $sum ='';
+ if ($log =~ m/^Summary:(.+?)$/m ) {
+ $sum = $1;
+ $sum =~ s/^\s+//;
+ $sum =~ s/\s+$//;
+ }
+
+ $msg = '';
+ if ($log =~ m/\n\n(.+)$/s) {
+ $msg = $1;
+ $msg =~ s/^\s+//;
+ $msg =~ s/\s+$//;
+ }
+
+
+ # cleanup the arrays
+ foreach my $ref ( (\@add, \@del, \@mod, \@ren) ) {
+ my @tmp = ();
+ while (my $t = pop @$ref) {
+ next unless length ($t);
+ next if $t =~ m!\{arch\}/!;
+ next if $t =~ m!\.arch-ids/!;
+ next if $t =~ m!\.arch-inventory$!;
+ # tla cat-archive-log will give us filenames with spaces as file\(sp)name - why?
+ # we can assume that any filename with \ indicates some pika escaping that we want to get rid of.
+ if ($t =~ /\\/ ){
+ $t = `tla escape --unescaped '$t'`;
+ }
+ push (@tmp, shell_quote($t));
+ }
+ @$ref = @tmp;
+ }
+
+ #print Dumper [$sum, $msg, \@add, \@del, \@mod, \@ren];
+ return ($sum, $msg, \@add, \@del, \@mod, \@ren);
+}
+
+# write/read a tag
+sub tag {
+ my ($tag, $commit) = @_;
+
+ if ($opt_o) {
+ $tag =~ s|/|--|g;
+ } else {
+ # don't use subdirs for tags yet, it could screw up other porcelains
+ $tag =~ s|/|,|g;
+ }
+
+ if ($commit) {
+ open(C,">","$git_dir/refs/tags/$tag")
+ or die "Cannot create tag $tag: $!\n";
+ print C "$commit\n"
+ or die "Cannot write tag $tag: $!\n";
+ close(C)
+ or die "Cannot write tag $tag: $!\n";
+ print " * Created tag '$tag' on '$commit'\n" if $opt_v;
+ } else { # read
+ open(C,"<","$git_dir/refs/tags/$tag")
+ or die "Cannot read tag $tag: $!\n";
+ $commit = <C>;
+ chomp $commit;
+ die "Error reading tag $tag: $!\n" unless length $commit == 40;
+ close(C)
+ or die "Cannot read tag $tag: $!\n";
+ return $commit;
+ }
+}
+
+# write/read a private tag
+# reads fail softly if the tag isn't there
+sub ptag {
+ my ($tag, $commit) = @_;
+
+ # don't use subdirs for tags yet, it could screw up other porcelains
+ $tag =~ s|/|,|g;
+
+ my $tag_file = "$ptag_dir/$tag";
+ my $tag_branch_dir = dirname($tag_file);
+ mkpath($tag_branch_dir) unless (-d $tag_branch_dir);
+
+ if ($commit) { # write
+ open(C,">",$tag_file)
+ or die "Cannot create tag $tag: $!\n";
+ print C "$commit\n"
+ or die "Cannot write tag $tag: $!\n";
+ close(C)
+ or die "Cannot write tag $tag: $!\n";
+ $rptags{$commit} = $tag
+ unless $tag =~ m/--base-0$/;
+ } else { # read
+ # if the tag isn't there, return 0
+ unless ( -s $tag_file) {
+ return 0;
+ }
+ open(C,"<",$tag_file)
+ or die "Cannot read tag $tag: $!\n";
+ $commit = <C>;
+ chomp $commit;
+ die "Error reading tag $tag: $!\n" unless length $commit == 40;
+ close(C)
+ or die "Cannot read tag $tag: $!\n";
+ unless (defined $rptags{$commit}) {
+ $rptags{$commit} = $tag;
+ }
+ return $commit;
+ }
+}
+
+sub find_parents {
+ #
+ # Identify what branches are merging into me
+ # and whether we are fully merged
+ # git-merge-base <headsha> <headsha> should tell
+ # me what the base of the merge should be
+ #
+ my $ps = shift;
+
+ my %branches; # holds an arrayref per branch
+ # the arrayref contains a list of
+ # merged patches between the base
+ # of the merge and the current head
+
+ my @parents; # parents found for this commit
+
+ # simple loop to split the merges
+ # per branch
+ foreach my $merge (@{$ps->{merges}}) {
+ my $branch = git_branchname($merge);
+ unless (defined $branches{$branch} ){
+ $branches{$branch} = [];
+ }
+ push @{$branches{$branch}}, $merge;
+ }
+
+ #
+ # foreach branch find a merge base and walk it to the
+ # head where we are, collecting the merged patchsets that
+ # Arch has recorded. Keep that in @have
+ # Compare that with the commits on the other branch
+ # between merge-base and the tip of the branch (@need)
+ # and see if we have a series of consecutive patches
+ # starting from the merge base. The tip of the series
+ # of consecutive patches merged is our new parent for
+ # that branch.
+ #
+ foreach my $branch (keys %branches) {
+
+ # check that we actually know about the branch
+ next unless -e "$git_dir/refs/heads/$branch";
+
+ my $mergebase = `git-merge-base $branch $ps->{branch}`;
+ if ($?) {
+ # Don't die here, Arch supports one-way cherry-picking
+ # between branches with no common base (or any relationship
+ # at all beforehand)
+ warn "Cannot find merge base for $branch and $ps->{branch}";
+ next;
+ }
+ chomp $mergebase;
+
+ # now walk up to the mergepoint collecting what patches we have
+ my $branchtip = git_rev_parse($ps->{branch});
+ my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`;
+ my %have; # collected merges this branch has
+ foreach my $merge (@{$ps->{merges}}) {
+ $have{$merge} = 1;
+ }
+ my %ancestorshave;
+ foreach my $par (@ancestors) {
+ $par = commitid2pset($par);
+ if (defined $par->{merges}) {
+ foreach my $merge (@{$par->{merges}}) {
+ $ancestorshave{$merge}=1;
+ }
+ }
+ }
+ # print "++++ Merges in $ps->{id} are....\n";
+ # my @have = sort keys %have; print Dumper(\@have);
+
+ # merge what we have with what ancestors have
+ %have = (%have, %ancestorshave);
+
+ # see what the remote branch has - these are the merges we
+ # will want to have in a consecutive series from the mergebase
+ my $otherbranchtip = git_rev_parse($branch);
+ my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`;
+ my @need;
+ foreach my $needps (@needraw) { # get the psets
+ $needps = commitid2pset($needps);
+ # git-rev-list will also
+ # list commits merged in via earlier
+ # merges. we are only interested in commits
+ # from the branch we're looking at
+ if ($branch eq $needps->{branch}) {
+ push @need, $needps->{id};
+ }
+ }
+
+ # print "++++ Merges from $branch we want are....\n";
+ # print Dumper(\@need);
+
+ my $newparent;
+ while (my $needed_commit = pop @need) {
+ if ($have{$needed_commit}) {
+ $newparent = $needed_commit;
+ } else {
+ last; # break out of the while
+ }
+ }
+ if ($newparent) {
+ push @parents, $newparent;
+ }
+
+
+ } # end foreach branch
+
+ # prune redundant parents
+ my %parents;
+ foreach my $p (@parents) {
+ $parents{$p} = 1;
+ }
+ foreach my $p (@parents) {
+ next unless exists $psets{$p}{merges};
+ next unless ref $psets{$p}{merges};
+ my @merges = @{$psets{$p}{merges}};
+ foreach my $merge (@merges) {
+ if ($parents{$merge}) {
+ delete $parents{$merge};
+ }
+ }
+ }
+ @parents = keys %parents;
+ @parents = map { " -p " . ptag($_) } @parents;
+ return @parents;
+}
+
+sub git_rev_parse {
+ my $name = shift;
+ my $val = `git-rev-parse $name`;
+ die "Error: git-rev-parse $name" if $?;
+ chomp $val;
+ return $val;
+}
+
+# resolve a SHA1 to a known patchset
+sub commitid2pset {
+ my $commitid = shift;
+ chomp $commitid;
+ my $name = $rptags{$commitid}
+ || die "Cannot find reverse tag mapping for $commitid";
+ $name =~ s|,|/|;
+ my $ps = $psets{$name}
+ || (print Dumper(sort keys %psets)) && die "Cannot find patchset for $name";
+ return $ps;
+}
diff --git a/git-bisect.sh b/git-bisect.sh
new file mode 100755
index 000000000..1ab2f187d
--- /dev/null
+++ b/git-bisect.sh
@@ -0,0 +1,223 @@
+#!/bin/sh
+. git-sh-setup || dir "Not a git archive"
+
+usage() {
+ echo >&2 'usage: git bisect [start|bad|good|next|reset|visualize]
+git bisect start reset bisect state and start bisection.
+git bisect bad [<rev>] mark <rev> a known-bad revision.
+git bisect good [<rev>...] mark <rev>... known-good revisions.
+git bisect next find next bisection to test and check it out.
+git bisect reset [<branch>] finish bisection search and go back to branch.
+git bisect visualize show bisect status in gitk.
+git bisect replay <logfile> replay bisection log
+git bisect log show bisect log.'
+ exit 1
+}
+
+bisect_autostart() {
+ test -d "$GIT_DIR/refs/bisect" || {
+ echo >&2 'You need to start by "git bisect start"'
+ if test -t 0
+ then
+ echo >&2 -n 'Do you want me to do it for you [Y/n]? '
+ read yesno
+ case "$yesno" in
+ [Nn]*)
+ exit ;;
+ esac
+ bisect_start
+ else
+ exit 1
+ fi
+ }
+}
+
+bisect_start() {
+ case "$#" in 0) ;; *) usage ;; esac
+ #
+ # Verify HEAD. If we were bisecting before this, reset to the
+ # top-of-line master first!
+ #
+ head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+ die "Bad HEAD - I need a symbolic ref"
+ case "$head" in
+ refs/heads/bisect*)
+ git checkout master || exit
+ ;;
+ refs/heads/*)
+ ;;
+ *)
+ die "Bad HEAD - strange symbolic ref"
+ ;;
+ esac
+
+ #
+ # Get rid of any old bisect state
+ #
+ rm -f "$GIT_DIR/refs/heads/bisect"
+ rm -rf "$GIT_DIR/refs/bisect/"
+ mkdir "$GIT_DIR/refs/bisect"
+ echo "git-bisect start" >"$GIT_DIR/BISECT_LOG"
+}
+
+bisect_bad() {
+ bisect_autostart
+ case "$#" in
+ 0)
+ rev=$(git-rev-parse --verify HEAD) ;;
+ 1)
+ rev=$(git-rev-parse --verify "$1") ;;
+ *)
+ usage ;;
+ esac || exit
+ echo "$rev" >"$GIT_DIR/refs/bisect/bad"
+ echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+ echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
+ bisect_auto_next
+}
+
+bisect_good() {
+ bisect_autostart
+ case "$#" in
+ 0) revs=$(git-rev-parse --verify HEAD) || exit ;;
+ *) revs=$(git-rev-parse --revs-only --no-flags "$@") &&
+ test '' != "$revs" || die "Bad rev input: $@" ;;
+ esac
+ for rev in $revs
+ do
+ rev=$(git-rev-parse --verify "$rev") || exit
+ echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
+ echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+ echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
+ done
+ bisect_auto_next
+}
+
+bisect_next_check() {
+ next_ok=no
+ test -f "$GIT_DIR/refs/bisect/bad" &&
+ case "$(cd "$GIT_DIR" && echo refs/bisect/good-*)" in
+ refs/bisect/good-\*) ;;
+ *) next_ok=yes ;;
+ esac
+ case "$next_ok,$1" in
+ no,) false ;;
+ no,fail)
+ echo >&2 'You need to give me at least one good and one bad revisions.'
+ exit 1 ;;
+ *)
+ true ;;
+ esac
+}
+
+bisect_auto_next() {
+ bisect_next_check && bisect_next || :
+}
+
+bisect_next() {
+ case "$#" in 0) ;; *) usage ;; esac
+ bisect_autostart
+ bisect_next_check fail
+ bad=$(git-rev-parse --verify refs/bisect/bad) &&
+ good=$(git-rev-parse --sq --revs-only --not \
+ $(cd "$GIT_DIR" && ls refs/bisect/good-*)) &&
+ rev=$(eval "git-rev-list --bisect $good $bad") || exit
+ if [ -z "$rev" ]; then
+ echo "$bad was both good and bad"
+ exit 1
+ fi
+ if [ "$rev" = "$bad" ]; then
+ echo "$rev is first bad commit"
+ git-diff-tree --pretty $rev
+ exit 0
+ fi
+ nr=$(eval "git-rev-list $rev $good" | wc -l) || exit
+ echo "Bisecting: $nr revisions left to test after this"
+ echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
+ git checkout new-bisect || exit
+ mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
+ GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
+ git-show-branch "$rev"
+}
+
+bisect_visualize() {
+ bisect_next_check fail
+ gitk bisect/bad --not `cd "$GIT_DIR/refs" && echo bisect/good-*`
+}
+
+bisect_reset() {
+ case "$#" in
+ 0) branch=master ;;
+ 1) test -f "$GIT_DIR/refs/heads/$1" || {
+ echo >&2 "$1 does not seem to be a valid branch"
+ exit 1
+ }
+ branch="$1" ;;
+ *)
+ usage ;;
+ esac
+ git checkout "$branch" &&
+ rm -fr "$GIT_DIR/refs/bisect"
+ rm -f "$GIT_DIR/refs/heads/bisect"
+ rm -f "$GIT_DIR/BISECT_LOG"
+}
+
+bisect_replay () {
+ test -r "$1" || {
+ echo >&2 "cannot read $1 for replaying"
+ exit 1
+ }
+ bisect_reset
+ while read bisect command rev
+ do
+ test "$bisect" = "git-bisect" || continue
+ case "$command" in
+ start)
+ bisect_start
+ ;;
+ good)
+ echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
+ echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+ echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
+ ;;
+ bad)
+ echo "$rev" >"$GIT_DIR/refs/bisect/bad"
+ echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+ echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
+ ;;
+ *)
+ echo >&2 "?? what are you talking about?"
+ exit 1 ;;
+ esac
+ done <"$1"
+ bisect_auto_next
+}
+
+case "$#" in
+0)
+ usage ;;
+*)
+ cmd="$1"
+ shift
+ case "$cmd" in
+ start)
+ bisect_start "$@" ;;
+ bad)
+ bisect_bad "$@" ;;
+ good)
+ bisect_good "$@" ;;
+ next)
+ # Not sure we want "next" at the UI level anymore.
+ bisect_next "$@" ;;
+ visualize)
+ bisect_visualize "$@" ;;
+ reset)
+ bisect_reset "$@" ;;
+ replay)
+ bisect_replay "$@" ;;
+ log)
+ cat "$GIT_DIR/BISECT_LOG" ;;
+ *)
+ usage ;;
+ esac
+esac
diff --git a/git-branch.sh b/git-branch.sh
new file mode 100755
index 000000000..2594518e9
--- /dev/null
+++ b/git-branch.sh
@@ -0,0 +1,117 @@
+#!/bin/sh
+
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ echo >&2 "usage: $(basename $0)"' [-d <branch>] | [[-f] <branch> [start-point]]
+
+If no arguments, show available branches and mark current branch with a star.
+If one argument, create a new branch <branchname> based off of current HEAD.
+If two arguments, create a new branch <branchname> based off of <start-point>.
+'
+ exit 1
+}
+
+headref=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD |
+ sed -e 's|^refs/heads/||')
+
+delete_branch () {
+ option="$1"
+ shift
+ for branch_name
+ do
+ case ",$headref," in
+ ",$branch_name,")
+ die "Cannot delete the branch you are on." ;;
+ ,,)
+ die "What branch are you on anyway?" ;;
+ esac
+ branch=$(cat "$GIT_DIR/refs/heads/$branch_name") &&
+ branch=$(git-rev-parse --verify "$branch^0") ||
+ die "Seriously, what branch are you talking about?"
+ case "$option" in
+ -D)
+ ;;
+ *)
+ mbs=$(git-merge-base -a "$branch" HEAD | tr '\012' ' ')
+ case " $mbs " in
+ *' '$branch' '*)
+ # the merge base of branch and HEAD contains branch --
+ # which means that the HEAD contains everything in the HEAD.
+ ;;
+ *)
+ echo >&2 "The branch '$branch_name' is not a strict subset of your current HEAD.
+ If you are sure you want to delete it, run 'git branch -D $branch_name'."
+ exit 1
+ ;;
+ esac
+ ;;
+ esac
+ rm -f "$GIT_DIR/refs/heads/$branch_name"
+ echo "Deleted branch $branch_name."
+ done
+ exit 0
+}
+
+force=
+while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
+do
+ case "$1" in
+ -d | -D)
+ delete_branch "$@"
+ exit
+ ;;
+ -f)
+ force="$1"
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ esac
+ shift
+done
+
+case "$#" in
+0)
+ git-rev-parse --symbolic --all |
+ sed -ne 's|^refs/heads/||p' |
+ sort |
+ while read ref
+ do
+ if test "$headref" = "$ref"
+ then
+ pfx='*'
+ else
+ pfx=' '
+ fi
+ echo "$pfx $ref"
+ done
+ exit 0 ;;
+1)
+ head=HEAD ;;
+2)
+ head="$2^0" ;;
+esac
+branchname="$1"
+
+rev=$(git-rev-parse --verify "$head") || exit
+
+git-check-ref-format "heads/$branchname" ||
+ die "we do not like '$branchname' as a branch name."
+
+if [ -e "$GIT_DIR/refs/heads/$branchname" ]
+then
+ if test '' = "$force"
+ then
+ die "$branchname already exists."
+ elif test "$branchname" = "$headref"
+ then
+ die "cannot force-update the current branch."
+ fi
+fi
+git update-ref "refs/heads/$branchname" $rev
+
diff --git a/git-checkout.sh b/git-checkout.sh
new file mode 100755
index 000000000..4c08f36b5
--- /dev/null
+++ b/git-checkout.sh
@@ -0,0 +1,139 @@
+#!/bin/sh
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ die "usage: git checkout [-f] [-b <new_branch>] [<branch>] [<paths>...]"
+}
+
+old=$(git-rev-parse HEAD)
+new=
+force=
+branch=
+newbranch=
+while [ "$#" != "0" ]; do
+ arg="$1"
+ shift
+ case "$arg" in
+ "-b")
+ newbranch="$1"
+ shift
+ [ -z "$newbranch" ] &&
+ die "git checkout: -b needs a branch name"
+ [ -e "$GIT_DIR/refs/heads/$newbranch" ] &&
+ die "git checkout: branch $newbranch already exists"
+ git-check-ref-format "heads/$newbranch" ||
+ die "we do not like '$newbranch' as a branch name."
+ ;;
+ "-f")
+ force=1
+ ;;
+ --)
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ if rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null)
+ then
+ if [ -z "$rev" ]; then
+ echo "unknown flag $arg"
+ exit 1
+ fi
+ new="$rev"
+ if [ -f "$GIT_DIR/refs/heads/$arg" ]; then
+ branch="$arg"
+ fi
+ elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
+ then
+ # checking out selected paths from a tree-ish.
+ new="$rev"
+ branch=
+ else
+ new=
+ branch=
+ set x "$arg" "$@"
+ shift
+ fi
+ break
+ ;;
+ esac
+done
+
+# The behaviour of the command with and without explicit path
+# parameters is quite different.
+#
+# Without paths, we are checking out everything in the work tree,
+# possibly switching branches. This is the traditional behaviour.
+#
+# With paths, we are _never_ switching branch, but checking out
+# the named paths from either index (when no rev is given),
+# or the named tree-ish (when rev is given).
+
+if test "$#" -ge 1
+then
+ if test '' != "$newbranch$force"
+ then
+ die "updating paths and switching branches or forcing are incompatible."
+ fi
+ if test '' != "$new"
+ then
+ # from a specific tree-ish; note that this is for
+ # rescuing paths and is never meant to remove what
+ # is not in the named tree-ish.
+ git-ls-tree -r "$new" "$@" |
+ sed -ne 's/^\([0-7]*\) blob \(.*\)$/\1 \2/p' |
+ git-update-index --index-info || exit $?
+ fi
+ git-checkout-index -f -u -- "$@"
+ exit $?
+else
+ # Make sure we did not fall back on $arg^{tree} codepath
+ # since we are not checking out from an arbitrary tree-ish,
+ # but switching branches.
+ if test '' != "$new"
+ then
+ git-rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
+ die "Cannot switch branch to a non-commit."
+ fi
+fi
+
+[ -z "$new" ] && new=$old
+
+# If we don't have an old branch that we're switching to,
+# and we don't have a new branch name for the target we
+# are switching to, then we'd better just be checking out
+# what we already had
+
+[ -z "$branch$newbranch" ] &&
+ [ "$new" != "$old" ] &&
+ die "git checkout: you need to specify a new branch name"
+
+if [ "$force" ]
+then
+ git-read-tree --reset $new &&
+ git-checkout-index -q -f -u -a
+else
+ git-update-index --refresh >/dev/null
+ git-read-tree -m -u $old $new
+fi
+
+#
+# Switch the HEAD pointer to the new branch if it we
+# checked out a branch head, and remove any potential
+# old MERGE_HEAD's (subsequent commits will clearly not
+# be based on them, since we re-set the index)
+#
+if [ "$?" -eq 0 ]; then
+ if [ "$newbranch" ]; then
+ leading=`expr "refs/heads/$newbranch" : '\(.*\)/'` &&
+ mkdir -p "$GIT_DIR/$leading" &&
+ echo $new >"$GIT_DIR/refs/heads/$newbranch" || exit
+ branch="$newbranch"
+ fi
+ [ "$branch" ] &&
+ GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
+ rm -f "$GIT_DIR/MERGE_HEAD"
+else
+ exit 1
+fi
diff --git a/git-cherry.sh b/git-cherry.sh
new file mode 100755
index 000000000..aad2e6171
--- /dev/null
+++ b/git-cherry.sh
@@ -0,0 +1,91 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano.
+#
+
+. git-sh-setup || die "Not a git archive."
+
+usage="usage: $0 "'[-v] <upstream> [<head>]
+
+ __*__*__*__*__> <upstream>
+ /
+ fork-point
+ \__+__+__+__+__+__+__+__> <head>
+
+Each commit between the fork-point and <head> is examined, and
+compared against the change each commit between the fork-point and
+<upstream> introduces. If the change seems to be in the upstream,
+it is shown on the standard output with prefix "+". Otherwise
+it is shown with prefix "-".
+'
+
+case "$1" in -v) verbose=t; shift ;; esac
+
+case "$#,$1" in
+1,*..*)
+ upstream=$(expr "$1" : '\(.*\)\.\.') ours=$(expr "$1" : '.*\.\.\(.*\)$')
+ set x "$upstream" "$ours"
+ shift ;;
+esac
+
+case "$#" in
+1) upstream=`git-rev-parse --verify "$1"` &&
+ ours=`git-rev-parse --verify HEAD` || exit
+ ;;
+2) upstream=`git-rev-parse --verify "$1"` &&
+ ours=`git-rev-parse --verify "$2"` || exit
+ ;;
+*) echo >&2 "$usage"; exit 1 ;;
+esac
+
+# Note that these list commits in reverse order;
+# not that the order in inup matters...
+inup=`git-rev-list ^$ours $upstream` &&
+ours=`git-rev-list $ours ^$upstream` || exit
+
+tmp=.cherry-tmp$$
+patch=$tmp-patch
+mkdir $patch
+trap "rm -rf $tmp-*" 0 1 2 3 15
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+
+for c in $inup
+do
+ git-diff-tree -p $c
+done | git-patch-id |
+while read id name
+do
+ echo $name >>$patch/$id
+done
+
+LF='
+'
+
+O=
+for c in $ours
+do
+ set x `git-diff-tree -p $c | git-patch-id`
+ if test "$2" != ""
+ then
+ if test -f "$patch/$2"
+ then
+ sign=-
+ else
+ sign=+
+ fi
+ case "$verbose" in
+ t)
+ c=$(git-rev-list --pretty=oneline --max-count=1 $c)
+ esac
+ case "$O" in
+ '') O="$sign $c" ;;
+ *) O="$sign $c$LF$O" ;;
+ esac
+ fi
+done
+case "$O" in
+'') ;;
+*) echo "$O" ;;
+esac
diff --git a/git-clone.sh b/git-clone.sh
new file mode 100755
index 000000000..c09979a7a
--- /dev/null
+++ b/git-clone.sh
@@ -0,0 +1,224 @@
+#!/bin/sh
+#
+# Copyright (c) 2005, Linus Torvalds
+# Copyright (c) 2005, Junio C Hamano
+#
+# Clone a repository into a different directory that does not yet exist.
+
+# See git-sh-setup why.
+unset CDPATH
+
+usage() {
+ echo >&2 "* git clone [-l [-s]] [-q] [-u <upload-pack>] [-n] <repo> [<dir>]"
+ exit 1
+}
+
+get_repo_base() {
+ (cd "$1" && (cd .git ; pwd)) 2> /dev/null
+}
+
+if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+ curl_extra_args="-k"
+fi
+
+http_fetch () {
+ # $1 = Remote, $2 = Local
+ curl -nsfL $curl_extra_args "$1" >"$2"
+}
+
+clone_dumb_http () {
+ # $1 - remote, $2 - local
+ cd "$2" &&
+ clone_tmp='.git/clone-tmp' &&
+ mkdir -p "$clone_tmp" || exit 1
+ http_fetch "$1/info/refs" "$clone_tmp/refs" &&
+ http_fetch "$1/objects/info/packs" "$clone_tmp/packs" || {
+ echo >&2 "Cannot get remote repository information.
+Perhaps git-update-server-info needs to be run there?"
+ exit 1;
+ }
+ while read type name
+ do
+ case "$type" in
+ P) ;;
+ *) continue ;;
+ esac &&
+
+ idx=`expr "$name" : '\(.*\)\.pack'`.idx
+ http_fetch "$1/objects/pack/$name" ".git/objects/pack/$name" &&
+ http_fetch "$1/objects/pack/$idx" ".git/objects/pack/$idx" &&
+ git-verify-pack ".git/objects/pack/$idx" || exit 1
+ done <"$clone_tmp/packs"
+
+ while read sha1 refname
+ do
+ name=`expr "$refname" : 'refs/\(.*\)'` &&
+ case "$name" in
+ *^*) ;;
+ *)
+ git-http-fetch -v -a -w "$name" "$name" "$1/" || exit 1
+ esac
+ done <"$clone_tmp/refs"
+ rm -fr "$clone_tmp"
+}
+
+quiet=
+use_local=no
+local_shared=no
+no_checkout=
+upload_pack=
+while
+ case "$#,$1" in
+ 0,*) break ;;
+ *,-n) no_checkout=yes ;;
+ *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;;
+ *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared)
+ local_shared=yes ;;
+ *,-q|*,--quiet) quiet=-q ;;
+ 1,-u|1,--upload-pack) usage ;;
+ *,-u|*,--upload-pack)
+ shift
+ upload_pack="--exec=$1" ;;
+ *,-*) usage ;;
+ *) break ;;
+ esac
+do
+ shift
+done
+
+# Turn the source into an absolute path if
+# it is local
+repo="$1"
+local=no
+if base=$(get_repo_base "$repo"); then
+ repo="$base"
+ local=yes
+fi
+
+dir="$2"
+# Try using "humanish" part of source repo if user didn't specify one
+[ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*/||g')
+[ -e "$dir" ] && echo "$dir already exists." && usage
+mkdir -p "$dir" &&
+D=$(
+ (cd "$dir" && git-init-db && pwd)
+) &&
+test -d "$D" || usage
+
+# We do local magic only when the user tells us to.
+case "$local,$use_local" in
+yes,yes)
+ ( cd "$repo/objects" ) || {
+ echo >&2 "-l flag seen but $repo is not local."
+ exit 1
+ }
+
+ case "$local_shared" in
+ no)
+ # See if we can hardlink and drop "l" if not.
+ sample_file=$(cd "$repo" && \
+ find objects -type f -print | sed -e 1q)
+
+ # objects directory should not be empty since we are cloning!
+ test -f "$repo/$sample_file" || exit
+
+ l=
+ if ln "$repo/$sample_file" "$D/.git/objects/sample" 2>/dev/null
+ then
+ l=l
+ fi &&
+ rm -f "$D/.git/objects/sample" &&
+ cd "$repo" &&
+ find objects -depth -print | cpio -puamd$l "$D/.git/" || exit 1
+ ;;
+ yes)
+ mkdir -p "$D/.git/objects/info"
+ {
+ test -f "$repo/objects/info/alternates" &&
+ cat "$repo/objects/info/alternates";
+ echo "$repo/objects"
+ } >"$D/.git/objects/info/alternates"
+ ;;
+ esac
+
+ # Make a duplicate of refs and HEAD pointer
+ HEAD=
+ if test -f "$repo/HEAD"
+ then
+ HEAD=HEAD
+ fi
+ (cd "$repo" && tar cf - refs $HEAD) |
+ (cd "$D/.git" && tar xf -) || exit 1
+ ;;
+*)
+ case "$repo" in
+ rsync://*)
+ rsync $quiet -av --ignore-existing \
+ --exclude info "$repo/objects/" "$D/.git/objects/" &&
+ rsync $quiet -av --ignore-existing \
+ --exclude info "$repo/refs/" "$D/.git/refs/" || exit
+
+ # Look at objects/info/alternates for rsync -- http will
+ # support it natively and git native ones will do it on the
+ # remote end. Not having that file is not a crime.
+ rsync -q "$repo/objects/info/alternates" \
+ "$D/.git/TMP_ALT" 2>/dev/null ||
+ rm -f "$D/.git/TMP_ALT"
+ if test -f "$D/.git/TMP_ALT"
+ then
+ ( cd "$D" &&
+ . git-parse-remote &&
+ resolve_alternates "$repo" <"./.git/TMP_ALT" ) |
+ while read alt
+ do
+ case "$alt" in 'bad alternate: '*) die "$alt";; esac
+ case "$quiet" in
+ '') echo >&2 "Getting alternate: $alt" ;;
+ esac
+ rsync $quiet -av --ignore-existing \
+ --exclude info "$alt" "$D/.git/objects" || exit
+ done
+ rm -f "$D/.git/TMP_ALT"
+ fi
+ ;;
+ http://*)
+ clone_dumb_http "$repo" "$D"
+ ;;
+ *)
+ cd "$D" && case "$upload_pack" in
+ '') git-clone-pack $quiet "$repo" ;;
+ *) git-clone-pack $quiet "$upload_pack" "$repo" ;;
+ esac
+ ;;
+ esac
+ ;;
+esac
+
+cd "$D" || exit
+
+if test -f ".git/HEAD"
+then
+ head_points_at=`git-symbolic-ref HEAD`
+ case "$head_points_at" in
+ refs/heads/*)
+ head_points_at=`expr "$head_points_at" : 'refs/heads/\(.*\)'`
+ mkdir -p .git/remotes &&
+ echo >.git/remotes/origin \
+ "URL: $repo
+Pull: $head_points_at:origin" &&
+ cp ".git/refs/heads/$head_points_at" .git/refs/heads/origin &&
+ find .git/refs/heads -type f -print |
+ while read ref
+ do
+ head=`expr "$ref" : '.git/refs/heads/\(.*\)'` &&
+ test "$head_points_at" = "$head" ||
+ test "origin" = "$head" ||
+ echo "Pull: ${head}:${head}"
+ done >>.git/remotes/origin
+ esac
+
+ case "$no_checkout" in
+ '')
+ git checkout
+ esac
+fi
diff --git a/git-commit.sh b/git-commit.sh
new file mode 100755
index 000000000..41955e8e6
--- /dev/null
+++ b/git-commit.sh
@@ -0,0 +1,244 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ die 'git commit [-a] [-s] [-v | --no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [<path>...]'
+}
+
+all= logfile= use_commit= no_edit= log_given= log_message= verify=t signoff=
+while case "$#" in 0) break;; esac
+do
+ case "$1" in
+ -a|--a|--al|--all)
+ all=t
+ shift ;;
+ -F=*|--f=*|--fi=*|--fil=*|--file=*)
+ log_given=t$log_given
+ logfile=`expr "$1" : '-[^=]*=\(.*\)'`
+ no_edit=t
+ shift ;;
+ -F|--f|--fi|--fil|--file)
+ case "$#" in 1) usage ;; esac; shift
+ log_given=t$log_given
+ logfile="$1"
+ no_edit=t
+ shift ;;
+ -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+ log_given=t$log_given
+ log_message=`expr "$1" : '-[^=]*=\(.*\)'`
+ no_edit=t
+ shift ;;
+ -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+ case "$#" in 1) usage ;; esac; shift
+ log_given=t$log_given
+ log_message="$1"
+ no_edit=t
+ shift ;;
+ -c=*|--ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
+ --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
+ --reedit-messag=*|--reedit-message=*)
+ log_given=t$log_given
+ use_commit=`expr "$1" : '-[^=]*=\(.*\)'`
+ shift ;;
+ -c|--ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
+ --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|--reedit-message)
+ case "$#" in 1) usage ;; esac; shift
+ log_given=t$log_given
+ use_commit="$1"
+ shift ;;
+ -C=*|--reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
+ --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
+ --reuse-message=*)
+ log_given=t$log_given
+ use_commit=`expr "$1" : '-[^=]*=\(.*\)'`
+ no_edit=t
+ shift ;;
+ -C|--reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
+ --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
+ case "$#" in 1) usage ;; esac; shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=t
+ shift ;;
+ -e|--e|--ed|--edi|--edit)
+ no_edit=
+ shift ;;
+ -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+ signoff=t
+ shift ;;
+ -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|--no-verify)
+ verify=
+ shift ;;
+ -v|--v|--ve|--ver|--veri|--verif|--verify)
+ verify=t
+ shift ;;
+ --)
+ shift
+ break ;;
+ -*)
+ usage ;;
+ *)
+ break ;;
+ esac
+done
+
+case "$log_given" in
+tt*)
+ die "Only one of -c/-C/-F/-m can be used." ;;
+esac
+
+case "$all,$#" in
+t,*)
+ git-diff-files --name-only -z |
+ git-update-index --remove -z --stdin
+ ;;
+,0)
+ ;;
+*)
+ git-diff-files --name-only -z -- "$@" |
+ git-update-index --remove -z --stdin
+ ;;
+esac || exit 1
+git-update-index -q --refresh || exit 1
+
+case "$verify" in
+t)
+ if test -x "$GIT_DIR"/hooks/pre-commit
+ then
+ "$GIT_DIR"/hooks/pre-commit || exit
+ fi
+esac
+
+if test "$log_message" != ''
+then
+ echo "$log_message"
+elif test "$logfile" != ""
+then
+ if test "$logfile" = -
+ then
+ test -t 0 &&
+ echo >&2 "(reading log message from standard input)"
+ cat
+ else
+ cat <"$logfile"
+ fi
+elif test "$use_commit" != ""
+then
+ git-cat-file commit "$use_commit" | sed -e '1,/^$/d'
+elif test -f "$GIT_DIR/MERGE_HEAD" && test -f "$GIT_DIR/MERGE_MSG"
+then
+ cat "$GIT_DIR/MERGE_MSG"
+fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
+
+case "$signoff" in
+t)
+ {
+ echo
+ git-var GIT_COMMITTER_IDENT | sed -e '
+ s/>.*/>/
+ s/^/Signed-off-by: /
+ '
+ } >>"$GIT_DIR"/COMMIT_EDITMSG
+ ;;
+esac
+
+if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+ echo "#"
+ echo "# It looks like your may be committing a MERGE."
+ echo "# If this is not correct, please remove the file"
+ echo "# $GIT_DIR/MERGE_HEAD"
+ echo "# and try again"
+ echo "#"
+fi >>"$GIT_DIR"/COMMIT_EDITMSG
+
+PARENTS="-p HEAD"
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
+then
+ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+ PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
+ fi
+ if test "$use_commit" != ""
+ then
+ pick_author_script='
+ /^author /{
+ h
+ s/^author \([^<]*\) <[^>]*> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+ g
+ s/^author [^<]* <\([^>]*\)> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+ g
+ s/^author [^<]* <[^>]*> \(.*\)$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+ q
+ }
+ '
+ set_author_env=`git-cat-file commit "$use_commit" |
+ LANG=C LC_ALL=C sed -ne "$pick_author_script"`
+ eval "$set_author_env"
+ export GIT_AUTHOR_NAME
+ export GIT_AUTHOR_EMAIL
+ export GIT_AUTHOR_DATE
+ fi
+else
+ if [ -z "$(git-ls-files)" ]; then
+ echo Nothing to commit 1>&2
+ exit 1
+ fi
+ PARENTS=""
+fi
+git-status >>"$GIT_DIR"/COMMIT_EDITMSG
+if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ]
+then
+ rm -f "$GIT_DIR/COMMIT_EDITMSG"
+ git-status
+ exit 1
+fi
+case "$no_edit" in
+'')
+ ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
+ ;;
+esac
+
+case "$verify" in
+t)
+ if test -x "$GIT_DIR"/hooks/commit-msg
+ then
+ "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
+ fi
+esac
+
+grep -v '^#' < "$GIT_DIR"/COMMIT_EDITMSG |
+git-stripspace > "$GIT_DIR"/COMMIT_MSG
+
+if cnt=`grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+ git-stripspace |
+ wc -l` &&
+ test 0 -lt $cnt
+then
+ tree=$(git-write-tree) &&
+ commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
+ git-update-ref HEAD $commit $current &&
+ rm -f -- "$GIT_DIR/MERGE_HEAD"
+else
+ echo >&2 "* no commit message? aborting commit."
+ false
+fi
+ret="$?"
+rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG"
+
+if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0
+then
+ "$GIT_DIR"/hooks/post-commit
+fi
+exit "$ret"
diff --git a/git-count-objects.sh b/git-count-objects.sh
new file mode 100755
index 000000000..843d2fd9f
--- /dev/null
+++ b/git-count-objects.sh
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+. git-sh-setup
+
+echo $(find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null | wc -l) objects, \
+$({
+ echo 0
+ # "no-such" is to help Darwin folks by not using xargs -r.
+ find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null |
+ xargs du -k "$GIT_DIR/objects/no-such" 2>/dev/null |
+ sed -e 's/[ ].*/ +/'
+ echo p
+} | dc) kilobytes
diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
new file mode 100755
index 000000000..5a8c01180
--- /dev/null
+++ b/git-cvsexportcommit.perl
@@ -0,0 +1,225 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Getopt::Std;
+use File::Temp qw(tempdir);
+use Data::Dumper;
+use File::Basename qw(basename);
+
+unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
+ die "GIT_DIR is not defined or is unreadable";
+}
+
+our ($opt_h, $opt_p, $opt_v, $opt_c );
+
+getopts('hpvc');
+
+$opt_h && usage();
+
+die "Need at least one commit identifier!" unless @ARGV;
+
+# setup a tempdir
+our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX',
+ TMPDIR => 1,
+ CLEANUP => 1);
+
+print Dumper(@ARGV);
+# resolve target commit
+my $commit;
+$commit = pop @ARGV;
+$commit = `git-rev-parse --verify "$commit"^0`;
+chomp $commit;
+if ($?) {
+ die "The commit reference $commit did not resolve!";
+}
+
+# resolve what parent we want
+my $parent;
+if (@ARGV) {
+ $parent = pop @ARGV;
+ $parent = `git-rev-parse --verify "$parent"^0"`;
+ chomp $parent;
+ if ($?) {
+ die "The parent reference did not resolve!";
+ }
+}
+
+# find parents from the commit itself
+my @commit = `git-cat-file commit $commit`;
+my @parents;
+foreach my $p (@commit) {
+ if ($p =~ m/^$/) { # end of commit headers, we're done
+ last;
+ }
+ if ($p =~ m/^parent (\w{40})$/) { # found a parent
+ push @parents, $1;
+ }
+}
+
+if ($parent) {
+ # double check that it's a valid parent
+ foreach my $p (@parents) {
+ my $found;
+ if ($p eq $parent) {
+ $found = 1;
+ last;
+ }; # found it
+ die "Did not find $parent in the parents for this commit!";
+ }
+} else { # we don't have a parent from the cmdline...
+ if (@parents == 1) { # it's safe to get it from the commit
+ $parent = $parents[0];
+ } else { # or perhaps not!
+ die "This commit has more than one parent -- please name the parent you want to use explicitly";
+ }
+}
+
+$opt_v && print "Applying to CVS commit $commit from parent $parent\n";
+
+# grab the commit message
+`git-cat-file commit $commit | sed -e '1,/^\$/d' > .msg`;
+$? && die "Error extracting the commit message";
+
+my (@afiles, @dfiles, @mfiles);
+my @files = `git-diff-tree -r $parent $commit`;
+print @files;
+$? && die "Error in git-diff-tree";
+foreach my $f (@files) {
+ chomp $f;
+ my @fields = split(m/\s+/, $f);
+ if ($fields[4] eq 'A') {
+ push @afiles, $fields[5];
+ }
+ if ($fields[4] eq 'M') {
+ push @mfiles, $fields[5];
+ }
+ if ($fields[4] eq 'R') {
+ push @dfiles, $fields[5];
+ }
+}
+$opt_v && print "The commit affects:\n ";
+$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n";
+undef @files; # don't need it anymore
+
+# check that the files are clean and up to date according to cvs
+my $dirty;
+foreach my $f (@afiles, @mfiles, @dfiles) {
+ # TODO:we need to handle removed in cvs and/or new (from git)
+ my $status = `cvs -q status "$f" | grep '^File: '`;
+
+ unless ($status =~ m/Status: Up-to-date$/) {
+ $dirty = 1;
+ warn "File $f not up to date in your CVS checkout!\n";
+ }
+}
+if ($dirty) {
+ die "Exiting: your CVS tree is not clean for this merge.";
+}
+
+###
+### NOTE: if you are planning to die() past this point
+### you MUST call cleanupcvs(@files) before die()
+###
+
+
+print "'Patching' binary files\n";
+
+my @bfiles = `git-diff-tree -p $parent $commit | grep '^Binary'`;
+@bfiles = map { chomp } @bfiles;
+foreach my $f (@bfiles) {
+ # check that the file in cvs matches the "old" file
+ # extract the file to $tmpdir and comparre with cmp
+ my $tree = `git-rev-parse $parent^{tree} `;
+ chomp $tree;
+ my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
+ chomp $blob;
+ `git-cat-file blob $blob > $tmpdir/blob`;
+ `cmp -q $f $tmpdir/blob`;
+ if ($?) {
+ warn "Binary file $f in CVS does not match parent.\n";
+ $dirty = 1;
+ next;
+ }
+
+ # replace with the new file
+ `git-cat-file blob $blob > $f`;
+
+ # TODO: something smart with file modes
+
+}
+if ($dirty) {
+ cleanupcvs(@files);
+ die "Exiting: Binary files in CVS do not match parent";
+}
+
+## apply non-binary changes
+my $fuzz = $opt_p ? 0 : 2;
+
+print "Patching non-binary files\n";
+print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;
+
+my $dirtypatch = 0;
+if (($? >> 8) == 2) {
+ cleanupcvs(@files);
+ die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually";
+} elsif (($? >> 8) == 1) { # some hunks failed to apply
+ $dirtypatch = 1;
+}
+
+foreach my $f (@afiles) {
+ `cvs add $f`;
+ if ($?) {
+ $dirty = 1;
+ warn "Failed to cvs add $f -- you may need to do it manually";
+ }
+}
+
+foreach my $f (@dfiles) {
+ `cvs rm -f $f`;
+ if ($?) {
+ $dirty = 1;
+ warn "Failed to cvs rm -f $f -- you may need to do it manually";
+ }
+}
+
+print "Commit to CVS\n";
+my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);
+my $cmd = "cvs commit -F .msg $commitfiles";
+
+if ($dirtypatch) {
+ print "NOTE: One or more hunks failed to apply cleanly.\n";
+ print "Resolve the conflicts and then commit using:\n";
+ print "\n $cmd\n\n";
+ exit(1);
+}
+
+
+if ($opt_c) {
+ print "Autocommit\n $cmd\n";
+ print `cvs commit -F .msg $commitfiles 2>&1`;
+ if ($?) {
+ cleanupcvs(@files);
+ die "Exiting: The commit did not succeed";
+ }
+ print "Committed successfully to CVS\n";
+} else {
+ print "Ready for you to commit, just run:\n\n $cmd\n";
+}
+sub usage {
+ print STDERR <<END;
+Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [ parent ] commit
+END
+ exit(1);
+}
+
+# ensure cvs is clean before we die
+sub cleanupcvs {
+ my @files = @_;
+ foreach my $f (@files) {
+ `cvs -q update -C "$f"`;
+ if ($?) {
+ warn "Warning! Failed to cleanup state of $f\n";
+ }
+ }
+}
+
diff --git a/git-cvsimport.perl b/git-cvsimport.perl
new file mode 100755
index 000000000..efe193439
--- /dev/null
+++ b/git-cvsimport.perl
@@ -0,0 +1,842 @@
+#!/usr/bin/perl -w
+
+# This tool is copyright (c) 2005, Matthias Urlichs.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to aggregate CVS check-ins into related changes.
+# Fortunately, "cvsps" does that for us; all we have to do is to parse
+# its output.
+#
+# Checking out the files is done by a single long-running CVS connection
+# / server process.
+#
+# The head revision is on branch "origin" by default.
+# You can change that with the '-o' option.
+
+use strict;
+use warnings;
+use Getopt::Std;
+use File::Spec;
+use File::Temp qw(tempfile);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use Time::Local;
+use IO::Socket;
+use IO::Pipe;
+use POSIX qw(strftime dup2);
+use IPC::Open2;
+
+$SIG{'PIPE'}="IGNORE";
+$ENV{'TZ'}="UTC";
+
+our($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M);
+
+sub usage() {
+ print STDERR <<END;
+Usage: ${\basename $0} # fetch/update GIT from CVS
+ [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT]
+ [-p opts-for-cvsps] [-C GIT_repository] [-z fuzz]
+ [-i] [-k] [-u] [-s subst] [-m] [-M regex] [CVS_module]
+END
+ exit(1);
+}
+
+getopts("hivmkuo:d:p:C:z:s:M:P:") or usage();
+usage if $opt_h;
+
+@ARGV <= 1 or usage();
+
+if($opt_d) {
+ $ENV{"CVSROOT"} = $opt_d;
+} elsif(-f 'CVS/Root') {
+ open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
+ $opt_d = <$f>;
+ chomp $opt_d;
+ close $f;
+ $ENV{"CVSROOT"} = $opt_d;
+} elsif($ENV{"CVSROOT"}) {
+ $opt_d = $ENV{"CVSROOT"};
+} else {
+ die "CVSROOT needs to be set";
+}
+$opt_o ||= "origin";
+$opt_s ||= "-";
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $cvs_tree;
+if ($#ARGV == 0) {
+ $cvs_tree = $ARGV[0];
+} elsif (-f 'CVS/Repository') {
+ open my $f, '<', 'CVS/Repository' or
+ die 'Failed to open CVS/Repository';
+ $cvs_tree = <$f>;
+ chomp $cvs_tree;
+ close $f;
+} else {
+ usage();
+}
+
+our @mergerx = ();
+if ($opt_m) {
+ @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+}
+if ($opt_M) {
+ push (@mergerx, qr/$opt_M/);
+}
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package CVSconn;
+# Basic CVS dialog.
+# We're only interested in connecting and downloading, so ...
+
+use File::Spec;
+use File::Temp qw(tempfile);
+use POSIX qw(strftime dup2);
+
+sub new {
+ my($what,$repo,$subdir) = @_;
+ $what=ref($what) if ref($what);
+
+ my $self = {};
+ $self->{'buffer'} = "";
+ bless($self,$what);
+
+ $repo =~ s#/+$##;
+ $self->{'fullrep'} = $repo;
+ $self->conn();
+
+ $self->{'subdir'} = $subdir;
+ $self->{'lines'} = undef;
+
+ return $self;
+}
+
+sub conn {
+ my $self = shift;
+ my $repo = $self->{'fullrep'};
+ if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+ my($user,$pass,$serv,$port) = ($1,$2,$3,$4);
+ $user="anonymous" unless defined $user;
+ my $rr2 = "-";
+ unless($port) {
+ $rr2 = ":pserver:$user\@$serv:$repo";
+ $port=2401;
+ }
+ my $rr = ":pserver:$user\@$serv:$port$repo";
+
+ unless($pass) {
+ open(H,$ENV{'HOME'}."/.cvspass") and do {
+ # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
+ while(<H>) {
+ chomp;
+ s/^\/\d+\s+//;
+ my ($w,$p) = split(/\s/,$_,2);
+ if($w eq $rr or $w eq $rr2) {
+ $pass = $p;
+ last;
+ }
+ }
+ };
+ }
+ $pass="A" unless $pass;
+
+ my $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
+ die "Socket to $serv: $!\n" unless defined $s;
+ $s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
+ or die "Write to $serv: $!\n";
+ $s->flush();
+
+ my $rep = <$s>;
+
+ if($rep ne "I LOVE YOU\n") {
+ $rep="<unknown>" unless $rep;
+ die "AuthReply: $rep\n";
+ }
+ $self->{'socketo'} = $s;
+ $self->{'socketi'} = $s;
+ } else { # local or ext: Fork off our own cvs server.
+ my $pr = IO::Pipe->new();
+ my $pw = IO::Pipe->new();
+ my $pid = fork();
+ die "Fork: $!\n" unless defined $pid;
+ my $cvs = 'cvs';
+ $cvs = $ENV{CVS_SERVER} if exists $ENV{CVS_SERVER};
+ my $rsh = 'rsh';
+ $rsh = $ENV{CVS_RSH} if exists $ENV{CVS_RSH};
+
+ my @cvs = ($cvs, 'server');
+ my ($local, $user, $host);
+ $local = $repo =~ s/:local://;
+ if (!$local) {
+ $repo =~ s/:ext://;
+ $local = !($repo =~ s/^(?:([^\@:]+)\@)?([^:]+)://);
+ ($user, $host) = ($1, $2);
+ }
+ if (!$local) {
+ if ($user) {
+ unshift @cvs, $rsh, '-l', $user, $host;
+ } else {
+ unshift @cvs, $rsh, $host;
+ }
+ }
+
+ unless($pid) {
+ $pr->writer();
+ $pw->reader();
+ dup2($pw->fileno(),0);
+ dup2($pr->fileno(),1);
+ $pr->close();
+ $pw->close();
+ exec(@cvs);
+ }
+ $pw->writer();
+ $pr->reader();
+ $self->{'socketo'} = $pw;
+ $self->{'socketi'} = $pr;
+ }
+ $self->{'socketo'}->write("Root $repo\n");
+
+ # Trial and error says that this probably is the minimum set
+ $self->{'socketo'}->write("Valid-responses ok error Valid-requests Mode M Mbinary E Checked-in Created Updated Merged Removed\n");
+
+ $self->{'socketo'}->write("valid-requests\n");
+ $self->{'socketo'}->flush();
+
+ chomp(my $rep=$self->readline());
+ if($rep !~ s/^Valid-requests\s*//) {
+ $rep="<unknown>" unless $rep;
+ die "Expected Valid-requests from server, but got: $rep\n";
+ }
+ chomp(my $res=$self->readline());
+ die "validReply: $res\n" if $res ne "ok";
+
+ $self->{'socketo'}->write("UseUnchanged\n") if $rep =~ /\bUseUnchanged\b/;
+ $self->{'repo'} = $repo;
+}
+
+sub readline {
+ my($self) = @_;
+ return $self->{'socketi'}->getline();
+}
+
+sub _file {
+ # Request a file with a given revision.
+ # Trial and error says this is a good way to do it. :-/
+ my($self,$fn,$rev) = @_;
+ $self->{'socketo'}->write("Argument -N\n") or return undef;
+ $self->{'socketo'}->write("Argument -P\n") or return undef;
+ # -kk: Linus' version doesn't use it - defaults to off
+ if ($opt_k) {
+ $self->{'socketo'}->write("Argument -kk\n") or return undef;
+ }
+ $self->{'socketo'}->write("Argument -r\n") or return undef;
+ $self->{'socketo'}->write("Argument $rev\n") or return undef;
+ $self->{'socketo'}->write("Argument --\n") or return undef;
+ $self->{'socketo'}->write("Argument $self->{'subdir'}/$fn\n") or return undef;
+ $self->{'socketo'}->write("Directory .\n") or return undef;
+ $self->{'socketo'}->write("$self->{'repo'}\n") or return undef;
+ # $self->{'socketo'}->write("Sticky T1.0\n") or return undef;
+ $self->{'socketo'}->write("co\n") or return undef;
+ $self->{'socketo'}->flush() or return undef;
+ $self->{'lines'} = 0;
+ return 1;
+}
+sub _line {
+ # Read a line from the server.
+ # ... except that 'line' may be an entire file. ;-)
+ my($self, $fh) = @_;
+ die "Not in lines" unless defined $self->{'lines'};
+
+ my $line;
+ my $res=0;
+ while(defined($line = $self->readline())) {
+ # M U gnupg-cvs-rep/AUTHORS
+ # Updated gnupg-cvs-rep/
+ # /daten/src/rsync/gnupg-cvs-rep/AUTHORS
+ # /AUTHORS/1.1///T1.1
+ # u=rw,g=rw,o=rw
+ # 0
+ # ok
+
+ if($line =~ s/^(?:Created|Updated) //) {
+ $line = $self->readline(); # path
+ $line = $self->readline(); # Entries line
+ my $mode = $self->readline(); chomp $mode;
+ $self->{'mode'} = $mode;
+ defined (my $cnt = $self->readline())
+ or die "EOF from server after 'Changed'\n";
+ chomp $cnt;
+ die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
+ $line="";
+ $res=0;
+ while($cnt) {
+ my $buf;
+ my $num = $self->{'socketi'}->read($buf,$cnt);
+ die "Server: Filesize $cnt: $num: $!\n" if not defined $num or $num<=0;
+ print $fh $buf;
+ $res += $num;
+ $cnt -= $num;
+ }
+ } elsif($line =~ s/^ //) {
+ print $fh $line;
+ $res += length($line);
+ } elsif($line =~ /^M\b/) {
+ # output, do nothing
+ } elsif($line =~ /^Mbinary\b/) {
+ my $cnt;
+ die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
+ chomp $cnt;
+ die "Duh: Mbinary $cnt" if $cnt !~ /^\d+$/ or $cnt<1;
+ $line="";
+ while($cnt) {
+ my $buf;
+ my $num = $self->{'socketi'}->read($buf,$cnt);
+ die "S: Mbinary $cnt: $num: $!\n" if not defined $num or $num<=0;
+ print $fh $buf;
+ $res += $num;
+ $cnt -= $num;
+ }
+ } else {
+ chomp $line;
+ if($line eq "ok") {
+ # print STDERR "S: ok (".length($res).")\n";
+ return $res;
+ } elsif($line =~ s/^E //) {
+ # print STDERR "S: $line\n";
+ } elsif($line =~ /^Remove-entry /i) {
+ $line = $self->readline(); # filename
+ $line = $self->readline(); # OK
+ chomp $line;
+ die "Unknown: $line" if $line ne "ok";
+ return -1;
+ } else {
+ die "Unknown: $line\n";
+ }
+ }
+ }
+}
+sub file {
+ my($self,$fn,$rev) = @_;
+ my $res;
+
+ my ($fh, $name) = tempfile('gitcvs.XXXXXX',
+ DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+ $self->_file($fn,$rev) and $res = $self->_line($fh);
+
+ if (!defined $res) {
+ # retry
+ $self->conn();
+ $self->_file($fn,$rev)
+ or die "No file command send\n";
+ $res = $self->_line($fh);
+ die "No input: $fn $rev\n" unless defined $res;
+ }
+ close ($fh);
+
+ if ($res eq '') {
+ die "Looks like the server has gone away while fetching $fn $rev -- exiting!";
+ }
+
+ return ($name, $res);
+}
+
+
+package main;
+
+my $cvs = CVSconn->new($opt_d, $cvs_tree);
+
+
+sub pdate($) {
+ my($d) = @_;
+ m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?#
+ or die "Unparseable date: $d\n";
+ my $y=$1; $y-=1900 if $y>1900;
+ return timegm($6||0,$5,$4,$3,$2-1,$y);
+}
+
+sub pmode($) {
+ my($mode) = @_;
+ my $m = 0;
+ my $mm = 0;
+ my $um = 0;
+ for my $x(split(//,$mode)) {
+ if($x eq ",") {
+ $m |= $mm&$um;
+ $mm = 0;
+ $um = 0;
+ } elsif($x eq "u") { $um |= 0700;
+ } elsif($x eq "g") { $um |= 0070;
+ } elsif($x eq "o") { $um |= 0007;
+ } elsif($x eq "r") { $mm |= 0444;
+ } elsif($x eq "w") { $mm |= 0222;
+ } elsif($x eq "x") { $mm |= 0111;
+ } elsif($x eq "=") { # do nothing
+ } else { die "Unknown mode: $mode\n";
+ }
+ }
+ $m |= $mm&$um;
+ return $m;
+}
+
+sub getwd() {
+ my $pwd = `pwd`;
+ chomp $pwd;
+ return $pwd;
+}
+
+
+sub get_headref($$) {
+ my $name = shift;
+ my $git_dir = shift;
+ my $sha;
+
+ if (open(C,"$git_dir/refs/heads/$name")) {
+ chomp($sha = <C>);
+ close(C);
+ length($sha) == 40
+ or die "Cannot get head id for $name ($sha): $!\n";
+ }
+ return $sha;
+}
+
+
+-d $git_tree
+ or mkdir($git_tree,0777)
+ or die "Could not create $git_tree: $!";
+chdir($git_tree);
+
+my $last_branch = "";
+my $orig_branch = "";
+my $forward_master = 0;
+my %branch_date;
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
+$ENV{"GIT_DIR"} = $git_dir;
+my $orig_git_index;
+$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
+my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
+ DIR => File::Spec->tmpdir());
+close ($git_ih);
+$ENV{GIT_INDEX_FILE} = $git_index;
+unless(-d $git_dir) {
+ system("git-init-db");
+ die "Cannot init the GIT db at $git_tree: $?\n" if $?;
+ system("git-read-tree");
+ die "Cannot init an empty tree: $?\n" if $?;
+
+ $last_branch = $opt_o;
+ $orig_branch = "";
+} else {
+ -f "$git_dir/refs/heads/$opt_o"
+ or die "Branch '$opt_o' does not exist.\n".
+ "Either use the correct '-o branch' option,\n".
+ "or import to a new repository.\n";
+
+ open(F, "git-symbolic-ref HEAD |") or
+ die "Cannot run git-symbolic-ref: $!\n";
+ chomp ($last_branch = <F>);
+ $last_branch = basename($last_branch);
+ close(F);
+ unless($last_branch) {
+ warn "Cannot read the last branch name: $! -- assuming 'master'\n";
+ $last_branch = "master";
+ }
+ $orig_branch = $last_branch;
+ if (-f "$git_dir/CVS2GIT_HEAD") {
+ die <<EOM;
+CVS2GIT_HEAD exists.
+Make sure your working directory corresponds to HEAD and remove CVS2GIT_HEAD.
+You may need to run
+
+ git-read-tree -m -u CVS2GIT_HEAD HEAD
+EOM
+ }
+ system('cp', "$git_dir/HEAD", "$git_dir/CVS2GIT_HEAD");
+
+ $forward_master =
+ $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
+ system('cmp', '-s', "$git_dir/refs/heads/master",
+ "$git_dir/refs/heads/$opt_o") == 0;
+
+ # populate index
+ system('git-read-tree', $last_branch);
+ die "read-tree failed: $?\n" if $?;
+
+ # Get the last import timestamps
+ opendir(D,"$git_dir/refs/heads");
+ while(defined(my $head = readdir(D))) {
+ next if $head =~ /^\./;
+ open(F,"$git_dir/refs/heads/$head")
+ or die "Bad head branch: $head: $!\n";
+ chomp(my $ftag = <F>);
+ close(F);
+ open(F,"git-cat-file commit $ftag |");
+ while(<F>) {
+ next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
+ $branch_date{$head} = $1;
+ last;
+ }
+ close(F);
+ }
+ closedir(D);
+}
+
+-d $git_dir
+ or die "Could not create git subdir ($git_dir).\n";
+
+my $pid = open(CVS,"-|");
+die "Cannot fork: $!\n" unless defined $pid;
+unless($pid) {
+ my @opt;
+ @opt = split(/,/,$opt_p) if defined $opt_p;
+ unshift @opt, '-z', $opt_z if defined $opt_z;
+ unshift @opt, '-q' unless defined $opt_v;
+ unless (defined($opt_p) && $opt_p =~ m/--no-cvs-direct/) {
+ push @opt, '--cvs-direct';
+ }
+ if ($opt_P) {
+ exec("cat", $opt_P);
+ } else {
+ exec("cvsps",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
+ die "Could not start cvsps: $!\n";
+ }
+}
+
+
+## cvsps output:
+#---------------------
+#PatchSet 314
+#Date: 1999/09/18 13:03:59
+#Author: wkoch
+#Branch: STABLE-BRANCH-1-0
+#Ancestor branch: HEAD
+#Tag: (none)
+#Log:
+# See ChangeLog: Sat Sep 18 13:03:28 CEST 1999 Werner Koch
+#Members:
+# README:1.57->1.57.2.1
+# VERSION:1.96->1.96.2.1
+#
+#---------------------
+
+my $state = 0;
+
+my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
+my(@old,@new);
+my $commit = sub {
+ my $pid;
+ while(@old) {
+ my @o2;
+ if(@old > 55) {
+ @o2 = splice(@old,0,50);
+ } else {
+ @o2 = @old;
+ @old = ();
+ }
+ system("git-update-index","--force-remove","--",@o2);
+ die "Cannot remove files: $?\n" if $?;
+ }
+ while(@new) {
+ my @n2;
+ if(@new > 12) {
+ @n2 = splice(@new,0,10);
+ } else {
+ @n2 = @new;
+ @new = ();
+ }
+ system("git-update-index","--add",
+ (map { ('--cacheinfo', @$_) } @n2));
+ die "Cannot add files: $?\n" if $?;
+ }
+
+ $pid = open(C,"-|");
+ die "Cannot fork: $!" unless defined $pid;
+ unless($pid) {
+ exec("git-write-tree");
+ die "Cannot exec git-write-tree: $!\n";
+ }
+ chomp(my $tree = <C>);
+ length($tree) == 40
+ or die "Cannot get tree id ($tree): $!\n";
+ close(C)
+ or die "Error running git-write-tree: $?\n";
+ print "Tree ID $tree\n" if $opt_v;
+
+ my $parent = "";
+ if(open(C,"$git_dir/refs/heads/$last_branch")) {
+ chomp($parent = <C>);
+ close(C);
+ length($parent) == 40
+ or die "Cannot get parent id ($parent): $!\n";
+ print "Parent ID $parent\n" if $opt_v;
+ }
+
+ my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+ my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+ $pid = fork();
+ die "Fork: $!\n" unless defined $pid;
+ unless($pid) {
+ $pr->writer();
+ $pw->reader();
+ open(OUT,">&STDOUT");
+ dup2($pw->fileno(),0);
+ dup2($pr->fileno(),1);
+ $pr->close();
+ $pw->close();
+
+ my @par = ();
+ @par = ("-p",$parent) if $parent;
+
+ # loose detection of merges
+ # based on the commit msg
+ foreach my $rx (@mergerx) {
+ if ($logmsg =~ $rx) {
+ my $mparent = $1;
+ if ($mparent eq 'HEAD') { $mparent = $opt_o };
+ if ( -e "$git_dir/refs/heads/$mparent") {
+ $mparent = get_headref($mparent, $git_dir);
+ push @par, '-p', $mparent;
+ print OUT "Merge parent branch: $mparent\n" if $opt_v;
+ }
+ }
+ }
+
+ exec("env",
+ "GIT_AUTHOR_NAME=$author_name",
+ "GIT_AUTHOR_EMAIL=$author_email",
+ "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+ "GIT_COMMITTER_NAME=$author_name",
+ "GIT_COMMITTER_EMAIL=$author_email",
+ "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+ "git-commit-tree", $tree,@par);
+ die "Cannot exec git-commit-tree: $!\n";
+ }
+ $pw->writer();
+ $pr->reader();
+
+ # compatibility with git2cvs
+ substr($logmsg,32767) = "" if length($logmsg) > 32767;
+ $logmsg =~ s/[\s\n]+\z//;
+
+ print $pw "$logmsg\n"
+ or die "Error writing to git-commit-tree: $!\n";
+ $pw->close();
+
+ print "Committed patch $patchset ($branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
+ chomp(my $cid = <$pr>);
+ length($cid) == 40
+ or die "Cannot get commit id ($cid): $!\n";
+ print "Commit ID $cid\n" if $opt_v;
+ $pr->close();
+
+ waitpid($pid,0);
+ die "Error running git-commit-tree: $?\n" if $?;
+
+ open(C,">$git_dir/refs/heads/$branch")
+ or die "Cannot open branch $branch for update: $!\n";
+ print C "$cid\n"
+ or die "Cannot write branch $branch for update: $!\n";
+ close(C)
+ or die "Cannot write branch $branch for update: $!\n";
+
+ if($tag) {
+ my($in, $out) = ('','');
+ my($xtag) = $tag;
+ $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
+ $xtag =~ tr/_/\./ if ( $opt_u );
+
+ my $pid = open2($in, $out, 'git-mktag');
+ print $out "object $cid\n".
+ "type commit\n".
+ "tag $xtag\n".
+ "tagger $author_name <$author_email>\n"
+ or die "Cannot create tag object $xtag: $!\n";
+ close($out)
+ or die "Cannot create tag object $xtag: $!\n";
+
+ my $tagobj = <$in>;
+ chomp $tagobj;
+
+ if ( !close($in) or waitpid($pid, 0) != $pid or
+ $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
+ die "Cannot create tag object $xtag: $!\n";
+ }
+
+
+ open(C,">$git_dir/refs/tags/$xtag")
+ or die "Cannot create tag $xtag: $!\n";
+ print C "$tagobj\n"
+ or die "Cannot write tag $xtag: $!\n";
+ close(C)
+ or die "Cannot write tag $xtag: $!\n";
+
+ print "Created tag '$xtag' on '$branch'\n" if $opt_v;
+ }
+};
+
+while(<CVS>) {
+ chomp;
+ if($state == 0 and /^-+$/) {
+ $state = 1;
+ } elsif($state == 0) {
+ $state = 1;
+ redo;
+ } elsif(($state==0 or $state==1) and s/^PatchSet\s+//) {
+ $patchset = 0+$_;
+ $state=2;
+ } elsif($state == 2 and s/^Date:\s+//) {
+ $date = pdate($_);
+ unless($date) {
+ print STDERR "Could not parse date: $_\n";
+ $state=0;
+ next;
+ }
+ $state=3;
+ } elsif($state == 3 and s/^Author:\s+//) {
+ s/\s+$//;
+ if (/^(.*?)\s+<(.*)>/) {
+ ($author_name, $author_email) = ($1, $2);
+ } else {
+ $author_name = $author_email = $_;
+ }
+ $state = 4;
+ } elsif($state == 4 and s/^Branch:\s+//) {
+ s/\s+$//;
+ s/[\/]/$opt_s/g;
+ $branch = $_;
+ $state = 5;
+ } elsif($state == 5 and s/^Ancestor branch:\s+//) {
+ s/\s+$//;
+ $ancestor = $_;
+ $ancestor = $opt_o if $ancestor eq "HEAD";
+ $state = 6;
+ } elsif($state == 5) {
+ $ancestor = undef;
+ $state = 6;
+ redo;
+ } elsif($state == 6 and s/^Tag:\s+//) {
+ s/\s+$//;
+ if($_ eq "(none)") {
+ $tag = undef;
+ } else {
+ $tag = $_;
+ }
+ $state = 7;
+ } elsif($state == 7 and /^Log:/) {
+ $logmsg = "";
+ $state = 8;
+ } elsif($state == 8 and /^Members:/) {
+ $branch = $opt_o if $branch eq "HEAD";
+ if(defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
+ # skip
+ print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
+ $state = 11;
+ next;
+ }
+ if($ancestor) {
+ if(-f "$git_dir/refs/heads/$branch") {
+ print STDERR "Branch $branch already exists!\n";
+ $state=11;
+ next;
+ }
+ unless(open(H,"$git_dir/refs/heads/$ancestor")) {
+ print STDERR "Branch $ancestor does not exist!\n";
+ $state=11;
+ next;
+ }
+ chomp(my $id = <H>);
+ close(H);
+ unless(open(H,"> $git_dir/refs/heads/$branch")) {
+ print STDERR "Could not create branch $branch: $!\n";
+ $state=11;
+ next;
+ }
+ print H "$id\n"
+ or die "Could not write branch $branch: $!";
+ close(H)
+ or die "Could not write branch $branch: $!";
+ }
+ if(($ancestor || $branch) ne $last_branch) {
+ print "Switching from $last_branch to $branch\n" if $opt_v;
+ system("git-read-tree", $branch);
+ die "read-tree failed: $?\n" if $?;
+ }
+ $last_branch = $branch if $branch ne $last_branch;
+ $state = 9;
+ } elsif($state == 8) {
+ $logmsg .= "$_\n";
+ } elsif($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
+# VERSION:1.96->1.96.2.1
+ my $init = ($2 eq "INITIAL");
+ my $fn = $1;
+ my $rev = $3;
+ $fn =~ s#^/+##;
+ my ($tmpname, $size) = $cvs->file($fn,$rev);
+ if($size == -1) {
+ push(@old,$fn);
+ print "Drop $fn\n" if $opt_v;
+ } else {
+ print "".($init ? "New" : "Update")." $fn: $size bytes\n" if $opt_v;
+ open my $F, '-|', "git-hash-object -w $tmpname"
+ or die "Cannot create object: $!\n";
+ my $sha = <$F>;
+ chomp $sha;
+ close $F;
+ my $mode = pmode($cvs->{'mode'});
+ push(@new,[$mode, $sha, $fn]); # may be resurrected!
+ }
+ unlink($tmpname);
+ } elsif($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
+ my $fn = $1;
+ $fn =~ s#^/+##;
+ push(@old,$fn);
+ print "Delete $fn\n" if $opt_v;
+ } elsif($state == 9 and /^\s*$/) {
+ $state = 10;
+ } elsif(($state == 9 or $state == 10) and /^-+$/) {
+ &$commit();
+ $state = 1;
+ } elsif($state == 11 and /^-+$/) {
+ $state = 1;
+ } elsif(/^-+$/) { # end of unknown-line processing
+ $state = 1;
+ } elsif($state != 11) { # ignore stuff when skipping
+ print "* UNKNOWN LINE * $_\n";
+ }
+}
+&$commit() if $branch and $state != 11;
+
+unlink($git_index);
+
+if (defined $orig_git_index) {
+ $ENV{GIT_INDEX_FILE} = $orig_git_index;
+} else {
+ delete $ENV{GIT_INDEX_FILE};
+}
+
+# Now switch back to the branch we were in before all of this happened
+if($orig_branch) {
+ print "DONE\n" if $opt_v;
+ system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+ if $forward_master;
+ unless ($opt_i) {
+ system('git-read-tree', '-m', '-u', 'CVS2GIT_HEAD', 'HEAD');
+ die "read-tree failed: $?\n" if $?;
+ }
+} else {
+ $orig_branch = "master";
+ print "DONE; creating $orig_branch branch\n" if $opt_v;
+ system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+ unless -f "$git_dir/refs/heads/master";
+ system('git-update-ref', 'HEAD', "$orig_branch");
+ unless ($opt_i) {
+ system('git checkout');
+ die "checkout failed: $?\n" if $?;
+ }
+}
+unlink("$git_dir/CVS2GIT_HEAD");
diff --git a/git-diff.sh b/git-diff.sh
new file mode 100755
index 000000000..b3ec84be6
--- /dev/null
+++ b/git-diff.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+
+rev=$(git-rev-parse --revs-only --no-flags --sq "$@") || exit
+flags=$(git-rev-parse --no-revs --flags --sq "$@")
+files=$(git-rev-parse --no-revs --no-flags --sq "$@")
+
+: ${flags:="'-M' '-p'"}
+
+# I often say 'git diff --cached -p' and get scolded by git-diff-files, but
+# obviously I mean 'git diff --cached -p HEAD' in that case.
+case "$rev" in
+'')
+ case " $flags " in
+ *" '--cached' "*)
+ rev='HEAD '
+ ;;
+ esac
+esac
+
+case "$rev" in
+?*' '?*' '?*)
+ echo >&2 "I don't understand"
+ exit 1
+ ;;
+?*' '^?*)
+ begin=$(expr "$rev" : '.*^.\([0-9a-f]*\).*') &&
+ end=$(expr "$rev" : '.\([0-9a-f]*\). .*') || exit
+ cmd="git-diff-tree $flags $begin $end -- $files"
+ ;;
+?*' '?*)
+ cmd="git-diff-tree $flags $rev -- $files"
+ ;;
+?*' ')
+ cmd="git-diff-index $flags $rev -- $files"
+ ;;
+'')
+ cmd="git-diff-files $flags -- $files"
+ ;;
+*)
+ die "I don't understand $*"
+ ;;
+esac
+
+eval "$cmd"
diff --git a/git-fetch.sh b/git-fetch.sh
new file mode 100755
index 000000000..6586e773e
--- /dev/null
+++ b/git-fetch.sh
@@ -0,0 +1,343 @@
+#!/bin/sh
+#
+. git-sh-setup || die "Not a git archive"
+. git-parse-remote
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+
+LF='
+'
+IFS="$LF"
+
+tags=
+append=
+force=
+verbose=
+update_head_ok=
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -a|--a|--ap|--app|--appe|--appen|--append)
+ append=t
+ ;;
+ -f|--f|--fo|--for|--forc|--force)
+ force=t
+ ;;
+ -t|--t|--ta|--tag|--tags)
+ tags=t
+ ;;
+ -u|--u|--up|--upd|--upda|--updat|--update|--update-|--update-h|\
+ --update-he|--update-hea|--update-head|--update-head-|\
+ --update-head-o|--update-head-ok)
+ update_head_ok=t
+ ;;
+ -v|--verbose)
+ verbose=Yes
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+case "$#" in
+0)
+ test -f "$GIT_DIR/branches/origin" ||
+ test -f "$GIT_DIR/remotes/origin" ||
+ die "Where do you want to fetch from today?"
+ set origin ;;
+esac
+
+remote_nick="$1"
+remote=$(get_remote_url "$@")
+refs=
+rref=
+rsync_slurped_objects=
+
+if test "" = "$append"
+then
+ : >"$GIT_DIR/FETCH_HEAD"
+fi
+
+append_fetch_head () {
+ head_="$1"
+ remote_="$2"
+ remote_name_="$3"
+ remote_nick_="$4"
+ local_name_="$5"
+ case "$6" in
+ t) not_for_merge_='not-for-merge' ;;
+ '') not_for_merge_= ;;
+ esac
+
+ # remote-nick is the URL given on the command line (or a shorthand)
+ # remote-name is the $GIT_DIR relative refs/ path we computed
+ # for this refspec.
+ case "$remote_name_" in
+ HEAD)
+ note_= ;;
+ refs/heads/*)
+ note_="$(expr "$remote_name_" : 'refs/heads/\(.*\)')"
+ note_="branch '$note_' of " ;;
+ refs/tags/*)
+ note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')"
+ note_="tag '$note_' of " ;;
+ *)
+ note_="$remote_name of " ;;
+ esac
+ remote_1_=$(expr "$remote_" : '\(.*\)\.git/*$') &&
+ remote_="$remote_1_"
+ note_="$note_$remote_"
+
+ # 2.6.11-tree tag would not be happy to be fed to resolve.
+ if git-cat-file commit "$head_" >/dev/null 2>&1
+ then
+ headc_=$(git-rev-parse --verify "$head_^0") || exit
+ echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD"
+ [ "$verbose" ] && echo >&2 "* committish: $head_"
+ [ "$verbose" ] && echo >&2 " $note_"
+ else
+ echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD"
+ [ "$verbose" ] && echo >&2 "* non-commit: $head_"
+ [ "$verbose" ] && echo >&2 " $note_"
+ fi
+ if test "$local_name_" != ""
+ then
+ # We are storing the head locally. Make sure that it is
+ # a fast forward (aka "reverse push").
+ fast_forward_local "$local_name_" "$head_" "$note_"
+ fi
+}
+
+fast_forward_local () {
+ mkdir -p "$(dirname "$GIT_DIR/$1")"
+ case "$1" in
+ refs/tags/*)
+ # Tags need not be pointing at commits so there
+ # is no way to guarantee "fast-forward" anyway.
+ if test -f "$GIT_DIR/$1"
+ then
+ if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
+ then
+ [ "$verbose" ] && echo >&2 "* $1: same as $3"
+ else
+ echo >&2 "* $1: updating with $3"
+ fi
+ else
+ echo >&2 "* $1: storing $3"
+ fi
+ git-update-ref "$1" "$2"
+ ;;
+
+ refs/heads/*)
+ # $1 is the ref being updated.
+ # $2 is the new value for the ref.
+ local=$(git-rev-parse --verify "$1^0" 2>/dev/null)
+ if test "$local"
+ then
+ # Require fast-forward.
+ mb=$(git-merge-base "$local" "$2") &&
+ case "$2,$mb" in
+ $local,*)
+ echo >&2 "* $1: same as $3"
+ ;;
+ *,$local)
+ echo >&2 "* $1: fast forward to $3"
+ git-update-ref "$1" "$2" "$local"
+ ;;
+ *)
+ false
+ ;;
+ esac || {
+ echo >&2 "* $1: does not fast forward to $3;"
+ case ",$force,$single_force," in
+ *,t,*)
+ echo >&2 " forcing update."
+ git-update-ref "$1" "$2" "$local"
+ ;;
+ *)
+ echo >&2 " not updating."
+ ;;
+ esac
+ }
+ else
+ echo >&2 "* $1: storing $3"
+ git-update-ref "$1" "$2"
+ fi
+ ;;
+ esac
+}
+
+case "$update_head_ok" in
+'')
+ orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+ ;;
+esac
+
+# If --tags (and later --heads or --all) is specified, then we are
+# not talking about defaults stored in Pull: line of remotes or
+# branches file, and just fetch those and refspecs explicitly given.
+# Otherwise we do what we always did.
+
+reflist=$(get_remote_refs_for_fetch "$@")
+if test "$tags"
+then
+ taglist=$(git-ls-remote --tags "$remote" |
+ sed -e '
+ /\^/d
+ s/^[^ ]* //
+ s/.*/&:&/')
+ if test "$#" -gt 1
+ then
+ # remote URL plus explicit refspecs; we need to merge them.
+ reflist="$reflist$LF$taglist"
+ else
+ # No explicit refspecs; fetch tags only.
+ reflist=$taglist
+ fi
+fi
+
+for ref in $reflist
+do
+ refs="$refs$LF$ref"
+
+ # These are relative path from $GIT_DIR, typically starting at refs/
+ # but may be HEAD
+ if expr "$ref" : '\.' >/dev/null
+ then
+ not_for_merge=t
+ ref=$(expr "$ref" : '\.\(.*\)')
+ else
+ not_for_merge=
+ fi
+ if expr "$ref" : '\+' >/dev/null
+ then
+ single_force=t
+ ref=$(expr "$ref" : '\+\(.*\)')
+ else
+ single_force=
+ fi
+ remote_name=$(expr "$ref" : '\([^:]*\):')
+ local_name=$(expr "$ref" : '[^:]*:\(.*\)')
+
+ rref="$rref$LF$remote_name"
+
+ # There are transports that can fetch only one head at a time...
+ case "$remote" in
+ http://* | https://*)
+ if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+ curl_extra_args="-k"
+ fi
+ remote_name_quoted=$(perl -e '
+ my $u = $ARGV[0];
+ $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
+ print "$u";
+ ' "$remote_name")
+ head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") &&
+ expr "$head" : "$_x40\$" >/dev/null ||
+ die "Failed to fetch $remote_name from $remote"
+ echo >&2 Fetching "$remote_name from $remote" using http
+ git-http-fetch -v -a "$head" "$remote/" || exit
+ ;;
+ rsync://*)
+ TMP_HEAD="$GIT_DIR/TMP_HEAD"
+ rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
+ head=$(git-rev-parse --verify TMP_HEAD)
+ rm -f "$TMP_HEAD"
+ test "$rsync_slurped_objects" || {
+ rsync -av --ignore-existing --exclude info \
+ "$remote/objects/" "$GIT_OBJECT_DIRECTORY/" || exit
+
+ # Look at objects/info/alternates for rsync -- http will
+ # support it natively and git native ones will do it on the remote
+ # end. Not having that file is not a crime.
+ rsync -q "$remote/objects/info/alternates" \
+ "$GIT_DIR/TMP_ALT" 2>/dev/null ||
+ rm -f "$GIT_DIR/TMP_ALT"
+ if test -f "$GIT_DIR/TMP_ALT"
+ then
+ resolve_alternates "$remote" <"$GIT_DIR/TMP_ALT" |
+ while read alt
+ do
+ case "$alt" in 'bad alternate: '*) die "$alt";; esac
+ echo >&2 "Getting alternate: $alt"
+ rsync -av --ignore-existing --exclude info \
+ "$alt" "$GIT_OBJECT_DIRECTORY/" || exit
+ done
+ rm -f "$GIT_DIR/TMP_ALT"
+ fi
+ rsync_slurped_objects=t
+ }
+ ;;
+ *)
+ # We will do git native transport with just one call later.
+ continue ;;
+ esac
+
+ append_fetch_head "$head" "$remote" \
+ "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+
+done
+
+case "$remote" in
+http://* | https://* | rsync://* )
+ ;; # we are already done.
+*)
+ IFS=" $LF"
+ (
+ git-fetch-pack "$remote" $rref || echo failed "$remote"
+ ) |
+ while read sha1 remote_name
+ do
+ case "$sha1" in
+ failed)
+ echo >&2 "Fetch failure: $remote"
+ exit 1 ;;
+ esac
+ found=
+ single_force=
+ for ref in $refs
+ do
+ case "$ref" in
+ +$remote_name:*)
+ single_force=t
+ not_for_merge=
+ found="$ref"
+ break ;;
+ .+$remote_name:*)
+ single_force=t
+ not_for_merge=t
+ found="$ref"
+ break ;;
+ .$remote_name:*)
+ not_for_merge=t
+ found="$ref"
+ break ;;
+ $remote_name:*)
+ not_for_merge=
+ found="$ref"
+ break ;;
+ esac
+ done
+ local_name=$(expr "$found" : '[^:]*:\(.*\)')
+ append_fetch_head "$sha1" "$remote" \
+ "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+ done || exit
+ ;;
+esac
+
+# If the original head was empty (i.e. no "master" yet), or
+# if we were told not to worry, we do not have to check.
+case ",$update_head_ok,$orig_head," in
+*,, | t,* )
+ ;;
+*)
+ curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+ if test "$curr_head" != "$orig_head"
+ then
+ git-update-ref HEAD "$orig_head"
+ die "Cannot fetch into the current branch."
+ fi
+ ;;
+esac
diff --git a/git-fmt-merge-msg.perl b/git-fmt-merge-msg.perl
new file mode 100755
index 000000000..778388e25
--- /dev/null
+++ b/git-fmt-merge-msg.perl
@@ -0,0 +1,95 @@
+#!/usr/bin/perl -w
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Read .git/FETCH_HEAD and make a human readable merge message
+# by grouping branches and tags together to form a single line.
+
+use strict;
+
+my @src;
+my %src;
+sub andjoin {
+ my ($label, $labels, $stuff) = @_;
+ my $l = scalar @$stuff;
+ my $m = '';
+ if ($l == 0) {
+ return ();
+ }
+ if ($l == 1) {
+ $m = "$label$stuff->[0]";
+ }
+ else {
+ $m = ("$labels" .
+ join (', ', @{$stuff}[0..$l-2]) .
+ " and $stuff->[-1]");
+ }
+ return ($m);
+}
+
+while (<>) {
+ my ($bname, $tname, $gname, $src);
+ chomp;
+ s/^[0-9a-f]* //;
+ next if (/^not-for-merge/);
+ s/^ //;
+ if (s/ of (.*)$//) {
+ $src = $1;
+ } else {
+ # Pulling HEAD
+ $src = $_;
+ $_ = 'HEAD';
+ }
+ if (! exists $src{$src}) {
+ push @src, $src;
+ $src{$src} = {
+ BRANCH => [],
+ TAG => [],
+ GENERIC => [],
+ # &1 == has HEAD.
+ # &2 == has others.
+ HEAD_STATUS => 0,
+ };
+ }
+ if (/^branch (.*)$/) {
+ push @{$src{$src}{BRANCH}}, $1;
+ $src{$src}{HEAD_STATUS} |= 2;
+ }
+ elsif (/^tag (.*)$/) {
+ push @{$src{$src}{TAG}}, $1;
+ $src{$src}{HEAD_STATUS} |= 2;
+ }
+ elsif (/^HEAD$/) {
+ $src{$src}{HEAD_STATUS} |= 1;
+ }
+ else {
+ push @{$src{$src}{GENERIC}}, $_;
+ $src{$src}{HEAD_STATUS} |= 2;
+ }
+}
+
+my @msg;
+for my $src (@src) {
+ if ($src{$src}{HEAD_STATUS} == 1) {
+ # Only HEAD is fetched, nothing else.
+ push @msg, $src;
+ next;
+ }
+ my @this;
+ if ($src{$src}{HEAD_STATUS} == 3) {
+ # HEAD is fetched among others.
+ push @this, andjoin('', '', ['HEAD']);
+ }
+ push @this, andjoin("branch ", "branches ",
+ $src{$src}{BRANCH});
+ push @this, andjoin("tag ", "tags ",
+ $src{$src}{TAG});
+ push @this, andjoin("commit ", "commits ",
+ $src{$src}{GENERIC});
+ my $this = join(', ', @this);
+ if ($src ne '.') {
+ $this .= " of $src";
+ }
+ push @msg, $this;
+}
+print "Merge ", join("; ", @msg), "\n";
diff --git a/git-format-patch.sh b/git-format-patch.sh
new file mode 100755
index 000000000..7ee5d328c
--- /dev/null
+++ b/git-format-patch.sh
@@ -0,0 +1,285 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+. git-sh-setup || die "Not a git archive."
+
+usage () {
+ echo >&2 "usage: $0"' [-n] [-o dir | --stdout] [--keep-subject] [--mbox]
+ [--check] [--signoff] [-<diff options>...]
+ [--help]
+ ( from..to ... | upstream [ our-head ] )
+
+Prepare each commit with its patch since our-head forked from upstream,
+one file per patch, for e-mail submission. Each output file is
+numbered sequentially from 1, and uses the first line of the commit
+message (massaged for pathname safety) as the filename.
+
+When -o is specified, output files are created in that directory; otherwise in
+the current working directory.
+
+When -n is specified, instead of "[PATCH] Subject", the first line is formatted
+as "[PATCH N/M] Subject", unless you have only one patch.
+
+When --mbox is specified, the output is formatted to resemble
+UNIX mailbox format, and can be concatenated together for processing
+with applymbox.
+'
+ exit 1
+}
+
+diff_opts=
+LF='
+'
+
+outdir=./
+while case "$#" in 0) break;; esac
+do
+ case "$1" in
+ -a|--a|--au|--aut|--auth|--autho|--author)
+ author=t ;;
+ -c|--c|--ch|--che|--chec|--check)
+ check=t ;;
+ -d|--d|--da|--dat|--date)
+ date=t ;;
+ -m|--m|--mb|--mbo|--mbox)
+ date=t author=t mbox=t ;;
+ -k|--k|--ke|--kee|--keep|--keep-|--keep-s|--keep-su|--keep-sub|\
+ --keep-subj|--keep-subje|--keep-subjec|--keep-subject)
+ keep_subject=t ;;
+ -n|--n|--nu|--num|--numb|--numbe|--number|--numbere|--numbered)
+ numbered=t ;;
+ -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+ signoff=t ;;
+ --st|--std|--stdo|--stdou|--stdout)
+ stdout=t mbox=t date=t author=t ;;
+ -o=*|--o=*|--ou=*|--out=*|--outp=*|--outpu=*|--output=*|--output-=*|\
+ --output-d=*|--output-di=*|--output-dir=*|--output-dire=*|\
+ --output-direc=*|--output-direct=*|--output-directo=*|\
+ --output-director=*|--output-directory=*)
+ outdir=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+ -o|--o|--ou|--out|--outp|--outpu|--output|--output-|--output-d|\
+ --output-di|--output-dir|--output-dire|--output-direc|--output-direct|\
+ --output-directo|--output-director|--output-directory)
+ case "$#" in 1) usage ;; esac; shift
+ outdir="$1" ;;
+ -h|--h|--he|--hel|--help)
+ usage
+ ;;
+ -*' '* | -*"$LF"* | -*' '*)
+ # Ignore diff option that has whitespace for now.
+ ;;
+ -*) diff_opts="$diff_opts$1 " ;;
+ *) break ;;
+ esac
+ shift
+done
+
+case "$keep_subject$numbered" in
+tt)
+ die '--keep-subject and --numbered are incompatible.' ;;
+esac
+
+tmp=.tmp-series$$
+trap 'rm -f $tmp-*' 0 1 2 3 15
+
+series=$tmp-series
+commsg=$tmp-commsg
+filelist=$tmp-files
+
+# Backward compatible argument parsing hack.
+#
+# Historically, we supported:
+# 1. "rev1" is equivalent to "rev1..HEAD"
+# 2. "rev1..rev2"
+# 3. "rev1" "rev2 is equivalent to "rev1..rev2"
+#
+# We want to take a sequence of "rev1..rev2" in general.
+# Also, "rev1.." should mean "rev1..HEAD"; git-diff users are
+# familiar with that syntax.
+
+case "$#,$1" in
+1,?*..?*)
+ # single "rev1..rev2"
+ ;;
+1,?*..)
+ # single "rev1.." should mean "rev1..HEAD"
+ set x "$1"HEAD
+ shift
+ ;;
+1,*)
+ # single rev1
+ set x "$1..HEAD"
+ shift
+ ;;
+2,?*..?*)
+ # not traditional "rev1" "rev2"
+ ;;
+2,*)
+ set x "$1..$2"
+ shift
+ ;;
+esac
+
+# Now we have what we want in $@
+for revpair
+do
+ case "$revpair" in
+ ?*..?*)
+ rev1=`expr "$revpair" : '\(.*\)\.\.'`
+ rev2=`expr "$revpair" : '.*\.\.\(.*\)'`
+ ;;
+ *)
+ usage
+ ;;
+ esac
+ git-rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
+ die "Not a valid rev $rev1 ($revpair)"
+ git-rev-parse --verify "$rev2^0" >/dev/null 2>&1 ||
+ die "Not a valid rev $rev2 ($revpair)"
+ git-cherry -v "$rev1" "$rev2" |
+ while read sign rev comment
+ do
+ case "$sign" in
+ '-')
+ echo >&2 "Merged already: $comment"
+ ;;
+ *)
+ echo $rev
+ ;;
+ esac
+ done
+done >$series
+
+me=`git-var GIT_AUTHOR_IDENT | sed -e 's/>.*/>/'`
+
+case "$outdir" in
+*/) ;;
+*) outdir="$outdir/" ;;
+esac
+test -d "$outdir" || mkdir -p "$outdir" || exit
+
+titleScript='
+ /./d
+ /^$/n
+ s/^\[PATCH[^]]*\] *//
+ s/[^-a-z.A-Z_0-9]/-/g
+ s/\.\.\.*/\./g
+ s/\.*$//
+ s/--*/-/g
+ s/^-//
+ s/-$//
+ s/$/./
+ p
+ q
+'
+
+whosepatchScript='
+/^author /{
+ s/author \(.*>\) \(.*\)$/au='\''\1'\'' ad='\''\2'\''/p
+ q
+}'
+
+process_one () {
+ mailScript='
+ /./d
+ /^$/n'
+ case "$keep_subject" in
+ t) ;;
+ *)
+ mailScript="$mailScript"'
+ s|^\[PATCH[^]]*\] *||
+ s|^|[PATCH'"$num"'] |'
+ ;;
+ esac
+ mailScript="$mailScript"'
+ s|^|Subject: |'
+ case "$mbox" in
+ t)
+ echo 'From nobody Mon Sep 17 00:00:00 2001' ;# UNIX "From" line
+ ;;
+ esac
+
+ eval "$(LANG=C LC_ALL=C sed -ne "$whosepatchScript" $commsg)"
+ test "$author,$au" = ",$me" || {
+ mailScript="$mailScript"'
+ a\
+From: '"$au"
+ }
+ test "$date,$au" = ",$me" || {
+ mailScript="$mailScript"'
+ a\
+Date: '"$ad"
+ }
+
+ mailScript="$mailScript"'
+ : body
+ p
+ n
+ b body'
+
+ (cat $commsg ; echo; echo) |
+ sed -ne "$mailScript" |
+ git-stripspace
+
+ test "$signoff" = "t" && {
+ offsigner=`git-var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/'`
+ line="Signed-off-by: $offsigner"
+ grep -q "^$line\$" $commsg || {
+ echo
+ echo "$line"
+ echo
+ }
+ }
+ echo
+ echo '---'
+ echo
+ git-diff-tree -p $diff_opts "$commit" | git-apply --stat --summary
+ echo
+ git-cat-file commit "$commit^" | sed -e 's/^tree /applies-to: /' -e q
+ git-diff-tree -p $diff_opts "$commit"
+ echo "---"
+ echo "@@GIT_VERSION@@"
+
+ case "$mbox" in
+ t)
+ echo
+ ;;
+ esac
+}
+
+total=`wc -l <$series | tr -dc "[0-9]"`
+i=1
+while read commit
+do
+ git-cat-file commit "$commit" | git-stripspace >$commsg
+ title=`sed -ne "$titleScript" <$commsg`
+ case "$numbered" in
+ '') num= ;;
+ *)
+ case $total in
+ 1) num= ;;
+ *) num=' '`printf "%d/%d" $i $total` ;;
+ esac
+ esac
+
+ file=`printf '%04d-%stxt' $i "$title"`
+ if test '' = "$stdout"
+ then
+ echo "* $file"
+ process_one >"$outdir$file"
+ if test t = "$check"
+ then
+ # This is slightly modified from Andrew Morton's Perfect Patch.
+ # Lines you introduce should not have trailing whitespace.
+ # Also check for an indentation that has SP before a TAB.
+ grep -n '^+\([ ]* .*\|.*[ ]\)$' "$outdir$file"
+ :
+ fi
+ else
+ echo >&2 "* $file"
+ process_one
+ fi
+ i=`expr "$i" + 1`
+done <$series
diff --git a/git-grep.sh b/git-grep.sh
new file mode 100755
index 000000000..44c16130b
--- /dev/null
+++ b/git-grep.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) Linus Torvalds, 2005
+#
+
+pattern=
+flags=()
+git_flags=()
+while : ; do
+ case "$1" in
+ --cached|--deleted|--others|--killed|\
+ --ignored|--exclude=*|\
+ --exclude-from=*|\--exclude-per-directory=*)
+ git_flags=("${git_flags[@]}" "$1")
+ ;;
+ -e)
+ pattern="$2"
+ shift
+ ;;
+ -A|-B|-C|-D|-d|-f|-m)
+ flags=("${flags[@]}" "$1" "$2")
+ shift
+ ;;
+ --)
+ # The rest are git-ls-files paths (or flags)
+ shift
+ break
+ ;;
+ -*)
+ flags=("${flags[@]}" "$1")
+ ;;
+ *)
+ if [ -z "$pattern" ]; then
+ pattern="$1"
+ shift
+ fi
+ break
+ ;;
+ esac
+ shift
+done
+[ "$pattern" ] || {
+ echo >&2 "usage: 'git grep <pattern> [pathspec*]'"
+ exit 1
+}
+git-ls-files -z "${git_flags[@]}" "$@" |
+ xargs -0 grep "${flags[@]}" -e "$pattern"
diff --git a/git-log.sh b/git-log.sh
new file mode 100755
index 000000000..b36c4e953
--- /dev/null
+++ b/git-log.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+
+# This one uses only subdirectory-aware commands, so no need to
+# include sh-setup-script.
+
+revs=$(git-rev-parse --revs-only --no-flags --default HEAD "$@") || exit
+[ "$revs" ] || {
+ echo >&2 "No HEAD ref"
+ exit 1
+}
+git-rev-list --pretty $(git-rev-parse --default HEAD "$@") |
+LESS=-S ${PAGER:-less}
diff --git a/git-lost-found.sh b/git-lost-found.sh
new file mode 100755
index 000000000..3892f5200
--- /dev/null
+++ b/git-lost-found.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+. git-sh-setup || die "Not a git archive."
+
+laf="$GIT_DIR/lost-found"
+rm -fr "$laf" && mkdir -p "$laf/commit" "$laf/other" || exit
+
+git fsck-objects |
+while read dangling type sha1
+do
+ case "$dangling" in
+ dangling)
+ if git-rev-parse --verify "$sha1^0" >/dev/null 2>/dev/null
+ then
+ dir="$laf/commit"
+ git-show-branch "$sha1"
+ else
+ dir="$laf/other"
+ fi
+ echo "$sha1" >"$dir/$sha1"
+ ;;
+ esac
+done
diff --git a/git-ls-remote.sh b/git-ls-remote.sh
new file mode 100755
index 000000000..f0f0b07f6
--- /dev/null
+++ b/git-ls-remote.sh
@@ -0,0 +1,109 @@
+#!/bin/sh
+#
+. git-sh-setup
+
+usage () {
+ echo >&2 "usage: $0 [--heads] [--tags] <repository> <refs>..."
+ exit 1;
+}
+
+while case "$#" in 0) break;; esac
+do
+ case "$1" in
+ -h|--h|--he|--hea|--head|--heads)
+ heads=heads; shift ;;
+ -t|--t|--ta|--tag|--tags)
+ tags=tags; shift ;;
+ --)
+ shift; break ;;
+ -*)
+ usage ;;
+ *)
+ break ;;
+ esac
+done
+
+case "$#" in 0) usage ;; esac
+
+case ",$heads,$tags," in
+,,,) heads=heads tags=tags other=other ;;
+esac
+
+. git-parse-remote
+peek_repo="$(get_remote_url "$@")"
+shift
+
+tmp=.ls-remote-$$
+trap "rm -fr $tmp-*" 0 1 2 3 15
+tmpdir=$tmp-d
+
+case "$peek_repo" in
+http://* | https://* )
+ if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+ curl_extra_args="-k"
+ fi
+ curl -nsf $curl_extra_args "$peek_repo/info/refs" ||
+ echo "failed slurping"
+ ;;
+
+rsync://* )
+ mkdir $tmpdir
+ rsync -rq "$peek_repo/refs" $tmpdir || {
+ echo "failed slurping"
+ exit
+ }
+ (cd $tmpdir && find refs -type f) |
+ while read path
+ do
+ cat "$tmpdir/$path" | tr -d '\012'
+ echo " $path"
+ done &&
+ rm -fr $tmpdir
+ ;;
+
+* )
+ git-peek-remote "$peek_repo" ||
+ echo "failed slurping"
+ ;;
+esac |
+sort -t ' ' -k 2 |
+while read sha1 path
+do
+ case "$sha1" in
+ failed)
+ die "Failed to find remote refs"
+ esac
+ case "$path" in
+ refs/heads/*)
+ group=heads ;;
+ refs/tags/*)
+ group=tags ;;
+ *)
+ group=other ;;
+ esac
+ case ",$heads,$tags,$other," in
+ *,$group,*)
+ ;;
+ *)
+ continue;;
+ esac
+ case "$#" in
+ 0)
+ match=yes ;;
+ *)
+ match=no
+ for pat
+ do
+ case "/$path" in
+ */$pat )
+ match=yes
+ break ;;
+ esac
+ done
+ esac
+ case "$match" in
+ no)
+ continue ;;
+ esac
+ echo "$sha1 $path"
+done
diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh
new file mode 100755
index 000000000..bb58e22a1
--- /dev/null
+++ b/git-merge-octopus.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Resolve two or more trees.
+#
+
+LF='
+'
+
+# The first parameters up to -- are merge bases; the rest are heads.
+bases= head= remotes= sep_seen=
+for arg
+do
+ case ",$sep_seen,$head,$arg," in
+ *,--,)
+ sep_seen=yes
+ ;;
+ ,yes,,*)
+ head=$arg
+ ;;
+ ,yes,*)
+ remotes="$remotes$arg "
+ ;;
+ *)
+ bases="$bases$arg "
+ ;;
+ esac
+done
+
+# Reject if this is not an Octopus -- resolve should be used instead.
+case "$remotes" in
+?*' '?*)
+ ;;
+*)
+ exit 2 ;;
+esac
+
+# MRC is the current "merge reference commit"
+# MRT is the current "merge result tree"
+
+MRC=$head MSG= PARENT="-p $head"
+MRT=$(git-write-tree)
+CNT=1 ;# counting our head
+NON_FF_MERGE=0
+for SHA1 in $remotes
+do
+ common=$(git-merge-base --all $MRC $SHA1) ||
+ die "Unable to find common commit with $SHA1"
+
+ case "$common" in
+ ?*"$LF"?*)
+ die "Not trivially mergeable."
+ ;;
+ $SHA1)
+ echo "Already up-to-date with $SHA1"
+ continue
+ ;;
+ esac
+
+ CNT=`expr $CNT + 1`
+ PARENT="$PARENT -p $SHA1"
+
+ if test "$common,$NON_FF_MERGE" = "$MRC,0"
+ then
+ # The first head being merged was a fast-forward.
+ # Advance MRC to the head being merged, and use that
+ # tree as the intermediate result of the merge.
+ # We still need to count this as part of the parent set.
+
+ echo "Fast forwarding to: $SHA1"
+ git-read-tree -u -m $head $SHA1 || exit
+ MRC=$SHA1 MRT=$(git-write-tree)
+ continue
+ fi
+
+ NON_FF_MERGE=1
+
+ echo "Trying simple merge with $SHA1"
+ git-read-tree -u -m $common $MRT $SHA1 || exit 2
+ next=$(git-write-tree 2>/dev/null)
+ if test $? -ne 0
+ then
+ echo "Simple merge did not work, trying automatic merge."
+ git-merge-index -o git-merge-one-file -a ||
+ exit 2 ; # Automatic merge failed; should not be doing Octopus
+ next=$(git-write-tree 2>/dev/null)
+ fi
+
+ # We have merged the other branch successfully. Ideally
+ # we could implement OR'ed heads in merge-base, and keep
+ # a list of commits we have merged so far in MRC to feed
+ # them to merge-base, but we approximate it by keep using
+ # the current MRC. We used to update it to $common, which
+ # was incorrectly doing AND'ed merge-base here, which was
+ # unneeded.
+
+ MRT=$next
+done
+
+exit 0
diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh
new file mode 100755
index 000000000..b08597de2
--- /dev/null
+++ b/git-merge-one-file.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+#
+# Copyright (c) Linus Torvalds, 2005
+#
+# This is the git per-file merge script, called with
+#
+# $1 - original file SHA1 (or empty)
+# $2 - file in branch1 SHA1 (or empty)
+# $3 - file in branch2 SHA1 (or empty)
+# $4 - pathname in repository
+# $5 - orignal file mode (or empty)
+# $6 - file in branch1 mode (or empty)
+# $7 - file in branch2 mode (or empty)
+#
+# Handle some trivial cases.. The _really_ trivial cases have
+# been handled already by git-read-tree, but that one doesn't
+# do any merges that might change the tree layout.
+
+case "${1:-.}${2:-.}${3:-.}" in
+#
+# Deleted in both or deleted in one and unchanged in the other
+#
+"$1.." | "$1.$1" | "$1$1.")
+ if [ "$2" ]; then
+ echo "Removing $4"
+ fi
+ if test -f "$4"; then
+ rm -f -- "$4"
+ fi &&
+ exec git-update-index --remove -- "$4"
+ ;;
+
+#
+# Added in one.
+#
+".$2." | "..$3" )
+ echo "Adding $4"
+ git-update-index --add --cacheinfo "$6$7" "$2$3" "$4" &&
+ exec git-checkout-index -u -f -- "$4"
+ ;;
+
+#
+# Added in both, identically (check for same permissions).
+#
+".$3$2")
+ if [ "$6" != "$7" ]; then
+ echo "ERROR: File $4 added identically in both branches,"
+ echo "ERROR: but permissions conflict $6->$7."
+ exit 1
+ fi
+ echo "Adding $4"
+ git-update-index --add --cacheinfo "$6" "$2" "$4" &&
+ exec git-checkout-index -u -f -- "$4"
+ ;;
+
+#
+# Modified in both, but differently.
+#
+"$1$2$3" | ".$2$3")
+ src2=`git-unpack-file $3`
+ case "$1" in
+ '')
+ echo "Added $4 in both, but differently."
+ # This extracts OUR file in $orig, and uses git-apply to
+ # remove lines that are unique to ours.
+ orig=`git-unpack-file $2`
+ sz0=`wc -c <"$orig"`
+ diff -u -La/$orig -Lb/$orig $orig $src2 | git-apply --no-add
+ sz1=`wc -c <"$orig"`
+
+ # If we do not have enough common material, it is not
+ # worth trying two-file merge using common subsections.
+ expr "$sz0" \< "$sz1" \* 2 >/dev/null || : >$orig
+ ;;
+ *)
+ echo "Auto-merging $4."
+ orig=`git-unpack-file $1`
+ ;;
+ esac
+
+ # We reset the index to the first branch, making
+ # git-diff-file useful
+ git-update-index --add --cacheinfo "$6" "$2" "$4"
+ git-checkout-index -u -f -- "$4" &&
+ merge "$4" "$orig" "$src2"
+ ret=$?
+ rm -f -- "$orig" "$src2"
+
+ if [ "$6" != "$7" ]; then
+ echo "ERROR: Permissions conflict: $5->$6,$7."
+ ret=1
+ fi
+ if [ "$1" = '' ]; then
+ ret=1
+ fi
+
+ if [ $ret -ne 0 ]; then
+ echo "ERROR: Merge conflict in $4."
+ exit 1
+ fi
+ exec git-update-index -- "$4"
+ ;;
+
+*)
+ echo "ERROR: $4: Not handling case $1 -> $2 -> $3"
+ ;;
+esac
+exit 1
diff --git a/git-merge-ours.sh b/git-merge-ours.sh
new file mode 100755
index 000000000..4f3d05388
--- /dev/null
+++ b/git-merge-ours.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Pretend we resolved the heads, but declare our tree trumps everybody else.
+#
+
+# We need to exit with 2 if the index does not match our HEAD tree,
+# because the current index is what we will be committing as the
+# merge result.
+
+test "$(git-diff-index --cached --name-status HEAD)" = "" || exit 2
+
+exit 0
diff --git a/git-merge-recursive.py b/git-merge-recursive.py
new file mode 100755
index 000000000..d7d36aa7d
--- /dev/null
+++ b/git-merge-recursive.py
@@ -0,0 +1,879 @@
+#!/usr/bin/python
+#
+# Copyright (C) 2005 Fredrik Kuivinen
+#
+
+import sys
+sys.path.append('''@@GIT_PYTHON_PATH@@''')
+
+import math, random, os, re, signal, tempfile, stat, errno, traceback
+from heapq import heappush, heappop
+from sets import Set
+
+from gitMergeCommon import *
+
+outputIndent = 0
+def output(*args):
+ sys.stdout.write(' '*outputIndent)
+ printList(args)
+
+originalIndexFile = os.environ.get('GIT_INDEX_FILE',
+ os.environ.get('GIT_DIR', '.git') + '/index')
+temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
+ '/merge-recursive-tmp-index'
+def setupIndex(temporary):
+ try:
+ os.unlink(temporaryIndexFile)
+ except OSError:
+ pass
+ if temporary:
+ newIndex = temporaryIndexFile
+ else:
+ newIndex = originalIndexFile
+ os.environ['GIT_INDEX_FILE'] = newIndex
+
+# This is a global variable which is used in a number of places but
+# only written to in the 'merge' function.
+
+# cacheOnly == True => Don't leave any non-stage 0 entries in the cache and
+# don't update the working directory.
+# False => Leave unmerged entries in the cache and update
+# the working directory.
+
+cacheOnly = False
+
+# The entry point to the merge code
+# ---------------------------------
+
+def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0):
+ '''Merge the commits h1 and h2, return the resulting virtual
+ commit object and a flag indicating the cleaness of the merge.'''
+ assert(isinstance(h1, Commit) and isinstance(h2, Commit))
+ assert(isinstance(graph, Graph))
+
+ global outputIndent
+
+ output('Merging:')
+ output(h1)
+ output(h2)
+ sys.stdout.flush()
+
+ ca = getCommonAncestors(graph, h1, h2)
+ output('found', len(ca), 'common ancestor(s):')
+ for x in ca:
+ output(x)
+ sys.stdout.flush()
+
+ mergedCA = ca[0]
+ for h in ca[1:]:
+ outputIndent = callDepth+1
+ [mergedCA, dummy] = merge(mergedCA, h,
+ 'Temporary merge branch 1',
+ 'Temporary merge branch 2',
+ graph, callDepth+1)
+ outputIndent = callDepth
+ assert(isinstance(mergedCA, Commit))
+
+ global cacheOnly
+ if callDepth == 0:
+ setupIndex(False)
+ cacheOnly = False
+ else:
+ setupIndex(True)
+ runProgram(['git-read-tree', h1.tree()])
+ cacheOnly = True
+
+ [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
+ branch1Name, branch2Name)
+
+ if clean or cacheOnly:
+ res = Commit(None, [h1, h2], tree=shaRes)
+ graph.addNode(res)
+ else:
+ res = None
+
+ return [res, clean]
+
+getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
+def getFilesAndDirs(tree):
+ files = Set()
+ dirs = Set()
+ out = runProgram(['git-ls-tree', '-r', '-z', tree])
+ for l in out.split('\0'):
+ m = getFilesRE.match(l)
+ if m:
+ if m.group(2) == 'tree':
+ dirs.add(m.group(4))
+ elif m.group(2) == 'blob':
+ files.add(m.group(4))
+
+ return [files, dirs]
+
+# Those two global variables are used in a number of places but only
+# written to in 'mergeTrees' and 'uniquePath'. They keep track of
+# every file and directory in the two branches that are about to be
+# merged.
+currentFileSet = None
+currentDirectorySet = None
+
+def mergeTrees(head, merge, common, branch1Name, branch2Name):
+ '''Merge the trees 'head' and 'merge' with the common ancestor
+ 'common'. The name of the head branch is 'branch1Name' and the name of
+ the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
+ where tree is the resulting tree and cleanMerge is True iff the
+ merge was clean.'''
+
+ assert(isSha(head) and isSha(merge) and isSha(common))
+
+ if common == merge:
+ output('Already uptodate!')
+ return [head, True]
+
+ if cacheOnly:
+ updateArg = '-i'
+ else:
+ updateArg = '-u'
+
+ [out, code] = runProgram(['git-read-tree', updateArg, '-m',
+ common, head, merge], returnCode = True)
+ if code != 0:
+ die('git-read-tree:', out)
+
+ [tree, code] = runProgram('git-write-tree', returnCode=True)
+ tree = tree.rstrip()
+ if code != 0:
+ global currentFileSet, currentDirectorySet
+ [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
+ [filesM, dirsM] = getFilesAndDirs(merge)
+ currentFileSet.union_update(filesM)
+ currentDirectorySet.union_update(dirsM)
+
+ entries = unmergedCacheEntries()
+ renamesHead = getRenames(head, common, head, merge, entries)
+ renamesMerge = getRenames(merge, common, head, merge, entries)
+
+ cleanMerge = processRenames(renamesHead, renamesMerge,
+ branch1Name, branch2Name)
+ for entry in entries:
+ if entry.processed:
+ continue
+ if not processEntry(entry, branch1Name, branch2Name):
+ cleanMerge = False
+
+ if cleanMerge or cacheOnly:
+ tree = runProgram('git-write-tree').rstrip()
+ else:
+ tree = None
+ else:
+ cleanMerge = True
+
+ return [tree, cleanMerge]
+
+# Low level file merging, update and removal
+# ------------------------------------------
+
+def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
+ branch1Name, branch2Name):
+
+ merge = False
+ clean = True
+
+ if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
+ clean = False
+ if stat.S_ISREG(aMode):
+ mode = aMode
+ sha = aSha
+ else:
+ mode = bMode
+ sha = bSha
+ else:
+ if aSha != oSha and bSha != oSha:
+ merge = True
+
+ if aMode == oMode:
+ mode = bMode
+ else:
+ mode = aMode
+
+ if aSha == oSha:
+ sha = bSha
+ elif bSha == oSha:
+ sha = aSha
+ elif stat.S_ISREG(aMode):
+ assert(stat.S_ISREG(bMode))
+
+ orig = runProgram(['git-unpack-file', oSha]).rstrip()
+ src1 = runProgram(['git-unpack-file', aSha]).rstrip()
+ src2 = runProgram(['git-unpack-file', bSha]).rstrip()
+ [out, code] = runProgram(['merge',
+ '-L', branch1Name + '/' + aPath,
+ '-L', 'orig/' + oPath,
+ '-L', branch2Name + '/' + bPath,
+ src1, orig, src2], returnCode=True)
+
+ sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
+ src1]).rstrip()
+
+ os.unlink(orig)
+ os.unlink(src1)
+ os.unlink(src2)
+
+ clean = (code == 0)
+ else:
+ assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
+ sha = aSha
+
+ if aSha != bSha:
+ clean = False
+
+ return [sha, mode, clean, merge]
+
+def updateFile(clean, sha, mode, path):
+ updateCache = cacheOnly or clean
+ updateWd = not cacheOnly
+
+ return updateFileExt(sha, mode, path, updateCache, updateWd)
+
+def updateFileExt(sha, mode, path, updateCache, updateWd):
+ if cacheOnly:
+ updateWd = False
+
+ if updateWd:
+ pathComponents = path.split('/')
+ for x in xrange(1, len(pathComponents)):
+ p = '/'.join(pathComponents[0:x])
+
+ try:
+ createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
+ except:
+ createDir = True
+
+ if createDir:
+ try:
+ os.mkdir(p)
+ except OSError, e:
+ die("Couldn't create directory", p, e.strerror)
+
+ prog = ['git-cat-file', 'blob', sha]
+ if stat.S_ISREG(mode):
+ try:
+ os.unlink(path)
+ except OSError:
+ pass
+ if mode & 0100:
+ mode = 0777
+ else:
+ mode = 0666
+ fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
+ proc = subprocess.Popen(prog, stdout=fd)
+ proc.wait()
+ os.close(fd)
+ elif stat.S_ISLNK(mode):
+ linkTarget = runProgram(prog)
+ os.symlink(linkTarget, path)
+ else:
+ assert(False)
+
+ if updateWd and updateCache:
+ runProgram(['git-update-index', '--add', '--', path])
+ elif updateCache:
+ runProgram(['git-update-index', '--add', '--cacheinfo',
+ '0%o' % mode, sha, path])
+
+def removeFile(clean, path):
+ updateCache = cacheOnly or clean
+ updateWd = not cacheOnly
+
+ if updateCache:
+ runProgram(['git-update-index', '--force-remove', '--', path])
+
+ if updateWd:
+ try:
+ os.unlink(path)
+ except OSError, e:
+ if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
+ raise
+
+def uniquePath(path, branch):
+ def fileExists(path):
+ try:
+ os.lstat(path)
+ return True
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ return False
+ else:
+ raise
+
+ branch = branch.replace('/', '_')
+ newPath = path + '~' + branch
+ suffix = 0
+ while newPath in currentFileSet or \
+ newPath in currentDirectorySet or \
+ fileExists(newPath):
+ suffix += 1
+ newPath = path + '~' + branch + '_' + str(suffix)
+ currentFileSet.add(newPath)
+ return newPath
+
+# Cache entry management
+# ----------------------
+
+class CacheEntry:
+ def __init__(self, path):
+ class Stage:
+ def __init__(self):
+ self.sha1 = None
+ self.mode = None
+
+ # Used for debugging only
+ def __str__(self):
+ if self.mode != None:
+ m = '0%o' % self.mode
+ else:
+ m = 'None'
+
+ if self.sha1:
+ sha1 = self.sha1
+ else:
+ sha1 = 'None'
+ return 'sha1: ' + sha1 + ' mode: ' + m
+
+ self.stages = [Stage(), Stage(), Stage(), Stage()]
+ self.path = path
+ self.processed = False
+
+ def __str__(self):
+ return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
+
+class CacheEntryContainer:
+ def __init__(self):
+ self.entries = {}
+
+ def add(self, entry):
+ self.entries[entry.path] = entry
+
+ def get(self, path):
+ return self.entries.get(path)
+
+ def __iter__(self):
+ return self.entries.itervalues()
+
+unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
+def unmergedCacheEntries():
+ '''Create a dictionary mapping file names to CacheEntry
+ objects. The dictionary contains one entry for every path with a
+ non-zero stage entry.'''
+
+ lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
+ lines.pop()
+
+ res = CacheEntryContainer()
+ for l in lines:
+ m = unmergedRE.match(l)
+ if m:
+ mode = int(m.group(1), 8)
+ sha1 = m.group(2)
+ stage = int(m.group(3))
+ path = m.group(4)
+
+ e = res.get(path)
+ if not e:
+ e = CacheEntry(path)
+ res.add(e)
+
+ e.stages[stage].mode = mode
+ e.stages[stage].sha1 = sha1
+ else:
+ die('Error: Merge program failed: Unexpected output from',
+ 'git-ls-files:', l)
+ return res
+
+lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
+def getCacheEntry(path, origTree, aTree, bTree):
+ '''Returns a CacheEntry object which doesn't have to correspond to
+ a real cache entry in Git's index.'''
+
+ def parse(out):
+ if out == '':
+ return [None, None]
+ else:
+ m = lsTreeRE.match(out)
+ if not m:
+ die('Unexpected output from git-ls-tree:', out)
+ elif m.group(2) == 'blob':
+ return [m.group(3), int(m.group(1), 8)]
+ else:
+ return [None, None]
+
+ res = CacheEntry(path)
+
+ [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
+ [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
+ [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
+
+ res.stages[1].sha1 = oSha
+ res.stages[1].mode = oMode
+ res.stages[2].sha1 = aSha
+ res.stages[2].mode = aMode
+ res.stages[3].sha1 = bSha
+ res.stages[3].mode = bMode
+
+ return res
+
+# Rename detection and handling
+# -----------------------------
+
+class RenameEntry:
+ def __init__(self,
+ src, srcSha, srcMode, srcCacheEntry,
+ dst, dstSha, dstMode, dstCacheEntry,
+ score):
+ self.srcName = src
+ self.srcSha = srcSha
+ self.srcMode = srcMode
+ self.srcCacheEntry = srcCacheEntry
+ self.dstName = dst
+ self.dstSha = dstSha
+ self.dstMode = dstMode
+ self.dstCacheEntry = dstCacheEntry
+ self.score = score
+
+ self.processed = False
+
+class RenameEntryContainer:
+ def __init__(self):
+ self.entriesSrc = {}
+ self.entriesDst = {}
+
+ def add(self, entry):
+ self.entriesSrc[entry.srcName] = entry
+ self.entriesDst[entry.dstName] = entry
+
+ def getSrc(self, path):
+ return self.entriesSrc.get(path)
+
+ def getDst(self, path):
+ return self.entriesDst.get(path)
+
+ def __iter__(self):
+ return self.entriesSrc.itervalues()
+
+parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
+def getRenames(tree, oTree, aTree, bTree, cacheEntries):
+ '''Get information of all renames which occured between 'oTree' and
+ 'tree'. We need the three trees in the merge ('oTree', 'aTree' and
+ 'bTree') to be able to associate the correct cache entries with
+ the rename information. 'tree' is always equal to either aTree or bTree.'''
+
+ assert(tree == aTree or tree == bTree)
+ inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
+ '-z', oTree, tree])
+
+ ret = RenameEntryContainer()
+ try:
+ recs = inp.split("\0")
+ recs.pop() # remove last entry (which is '')
+ it = recs.__iter__()
+ while True:
+ rec = it.next()
+ m = parseDiffRenamesRE.match(rec)
+
+ if not m:
+ die('Unexpected output from git-diff-tree:', rec)
+
+ srcMode = int(m.group(1), 8)
+ dstMode = int(m.group(2), 8)
+ srcSha = m.group(3)
+ dstSha = m.group(4)
+ score = m.group(5)
+ src = it.next()
+ dst = it.next()
+
+ srcCacheEntry = cacheEntries.get(src)
+ if not srcCacheEntry:
+ srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
+ cacheEntries.add(srcCacheEntry)
+
+ dstCacheEntry = cacheEntries.get(dst)
+ if not dstCacheEntry:
+ dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
+ cacheEntries.add(dstCacheEntry)
+
+ ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
+ dst, dstSha, dstMode, dstCacheEntry,
+ score))
+ except StopIteration:
+ pass
+ return ret
+
+def fmtRename(src, dst):
+ srcPath = src.split('/')
+ dstPath = dst.split('/')
+ path = []
+ endIndex = min(len(srcPath), len(dstPath)) - 1
+ for x in range(0, endIndex):
+ if srcPath[x] == dstPath[x]:
+ path.append(srcPath[x])
+ else:
+ endIndex = x
+ break
+
+ if len(path) > 0:
+ return '/'.join(path) + \
+ '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
+ '/'.join(dstPath[endIndex:]) + '}'
+ else:
+ return src + ' => ' + dst
+
+def processRenames(renamesA, renamesB, branchNameA, branchNameB):
+ srcNames = Set()
+ for x in renamesA:
+ srcNames.add(x.srcName)
+ for x in renamesB:
+ srcNames.add(x.srcName)
+
+ cleanMerge = True
+ for path in srcNames:
+ if renamesA.getSrc(path):
+ renames1 = renamesA
+ renames2 = renamesB
+ branchName1 = branchNameA
+ branchName2 = branchNameB
+ else:
+ renames1 = renamesB
+ renames2 = renamesA
+ branchName1 = branchNameB
+ branchName2 = branchNameA
+
+ ren1 = renames1.getSrc(path)
+ ren2 = renames2.getSrc(path)
+
+ ren1.dstCacheEntry.processed = True
+ ren1.srcCacheEntry.processed = True
+
+ if ren1.processed:
+ continue
+
+ ren1.processed = True
+ removeFile(True, ren1.srcName)
+ if ren2:
+ # Renamed in 1 and renamed in 2
+ assert(ren1.srcName == ren2.srcName)
+ ren2.dstCacheEntry.processed = True
+ ren2.processed = True
+
+ if ren1.dstName != ren2.dstName:
+ output('CONFLICT (rename/rename): Rename',
+ fmtRename(path, ren1.dstName), 'in branch', branchName1,
+ 'rename', fmtRename(path, ren2.dstName), 'in',
+ branchName2)
+ cleanMerge = False
+
+ if ren1.dstName in currentDirectorySet:
+ dstName1 = uniquePath(ren1.dstName, branchName1)
+ output(ren1.dstName, 'is a directory in', branchName2,
+ 'adding as', dstName1, 'instead.')
+ removeFile(False, ren1.dstName)
+ else:
+ dstName1 = ren1.dstName
+
+ if ren2.dstName in currentDirectorySet:
+ dstName2 = uniquePath(ren2.dstName, branchName2)
+ output(ren2.dstName, 'is a directory in', branchName1,
+ 'adding as', dstName2, 'instead.')
+ removeFile(False, ren2.dstName)
+ else:
+ dstName2 = ren1.dstName
+
+ updateFile(False, ren1.dstSha, ren1.dstMode, dstName1)
+ updateFile(False, ren2.dstSha, ren2.dstMode, dstName2)
+ else:
+ [resSha, resMode, clean, merge] = \
+ mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
+ ren1.dstName, ren1.dstSha, ren1.dstMode,
+ ren2.dstName, ren2.dstSha, ren2.dstMode,
+ branchName1, branchName2)
+
+ if merge or not clean:
+ output('Renaming', fmtRename(path, ren1.dstName))
+
+ if merge:
+ output('Auto-merging', ren1.dstName)
+
+ if not clean:
+ output('CONFLICT (content): merge conflict in',
+ ren1.dstName)
+ cleanMerge = False
+
+ if not cacheOnly:
+ updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
+ updateCache=True, updateWd=False)
+ updateFile(clean, resSha, resMode, ren1.dstName)
+ else:
+ # Renamed in 1, maybe changed in 2
+ if renamesA == renames1:
+ stage = 3
+ else:
+ stage = 2
+
+ srcShaOtherBranch = ren1.srcCacheEntry.stages[stage].sha1
+ srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
+
+ dstShaOtherBranch = ren1.dstCacheEntry.stages[stage].sha1
+ dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
+
+ tryMerge = False
+
+ if ren1.dstName in currentDirectorySet:
+ newPath = uniquePath(ren1.dstName, branchName1)
+ output('CONFLICT (rename/directory): Rename',
+ fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
+ 'directory', ren1.dstName, 'added in', branchName2)
+ output('Renaming', ren1.srcName, 'to', newPath, 'instead')
+ cleanMerge = False
+ removeFile(False, ren1.dstName)
+ updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
+ elif srcShaOtherBranch == None:
+ output('CONFLICT (rename/delete): Rename',
+ fmtRename(ren1.srcName, ren1.dstName), 'in',
+ branchName1, 'and deleted in', branchName2)
+ cleanMerge = False
+ updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
+ elif dstShaOtherBranch:
+ newPath = uniquePath(ren1.dstName, branchName2)
+ output('CONFLICT (rename/add): Rename',
+ fmtRename(ren1.srcName, ren1.dstName), 'in',
+ branchName1 + '.', ren1.dstName, 'added in', branchName2)
+ output('Adding as', newPath, 'instead')
+ updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
+ cleanMerge = False
+ tryMerge = True
+ elif renames2.getDst(ren1.dstName):
+ dst2 = renames2.getDst(ren1.dstName)
+ newPath1 = uniquePath(ren1.dstName, branchName1)
+ newPath2 = uniquePath(dst2.dstName, branchName2)
+ output('CONFLICT (rename/rename): Rename',
+ fmtRename(ren1.srcName, ren1.dstName), 'in',
+ branchName1+'. Rename',
+ fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
+ output('Renaming', ren1.srcName, 'to', newPath1, 'and',
+ dst2.srcName, 'to', newPath2, 'instead')
+ removeFile(False, ren1.dstName)
+ updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
+ updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
+ dst2.processed = True
+ cleanMerge = False
+ else:
+ tryMerge = True
+
+ if tryMerge:
+ [resSha, resMode, clean, merge] = \
+ mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
+ ren1.dstName, ren1.dstSha, ren1.dstMode,
+ ren1.srcName, srcShaOtherBranch, srcModeOtherBranch,
+ branchName1, branchName2)
+
+ if merge or not clean:
+ output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
+
+ if merge:
+ output('Auto-merging', ren1.dstName)
+
+ if not clean:
+ output('CONFLICT (rename/modify): Merge conflict in',
+ ren1.dstName)
+ cleanMerge = False
+
+ if not cacheOnly:
+ updateFileExt(ren1.dstSha, ren1.dstMode, ren1.dstName,
+ updateCache=True, updateWd=False)
+ updateFile(clean, resSha, resMode, ren1.dstName)
+
+ return cleanMerge
+
+# Per entry merge function
+# ------------------------
+
+def processEntry(entry, branch1Name, branch2Name):
+ '''Merge one cache entry.'''
+
+ debug('processing', entry.path, 'clean cache:', cacheOnly)
+
+ cleanMerge = True
+
+ path = entry.path
+ oSha = entry.stages[1].sha1
+ oMode = entry.stages[1].mode
+ aSha = entry.stages[2].sha1
+ aMode = entry.stages[2].mode
+ bSha = entry.stages[3].sha1
+ bMode = entry.stages[3].mode
+
+ assert(oSha == None or isSha(oSha))
+ assert(aSha == None or isSha(aSha))
+ assert(bSha == None or isSha(bSha))
+
+ assert(oMode == None or type(oMode) is int)
+ assert(aMode == None or type(aMode) is int)
+ assert(bMode == None or type(bMode) is int)
+
+ if (oSha and (not aSha or not bSha)):
+ #
+ # Case A: Deleted in one
+ #
+ if (not aSha and not bSha) or \
+ (aSha == oSha and not bSha) or \
+ (not aSha and bSha == oSha):
+ # Deleted in both or deleted in one and unchanged in the other
+ if aSha:
+ output('Removing', path)
+ removeFile(True, path)
+ else:
+ # Deleted in one and changed in the other
+ cleanMerge = False
+ if not aSha:
+ output('CONFLICT (delete/modify):', path, 'deleted in',
+ branch1Name, 'and modified in', branch2Name + '.',
+ 'Version', branch2Name, 'of', path, 'left in tree.')
+ mode = bMode
+ sha = bSha
+ else:
+ output('CONFLICT (modify/delete):', path, 'deleted in',
+ branch2Name, 'and modified in', branch1Name + '.',
+ 'Version', branch1Name, 'of', path, 'left in tree.')
+ mode = aMode
+ sha = aSha
+
+ updateFile(False, sha, mode, path)
+
+ elif (not oSha and aSha and not bSha) or \
+ (not oSha and not aSha and bSha):
+ #
+ # Case B: Added in one.
+ #
+ if aSha:
+ addBranch = branch1Name
+ otherBranch = branch2Name
+ mode = aMode
+ sha = aSha
+ conf = 'file/directory'
+ else:
+ addBranch = branch2Name
+ otherBranch = branch1Name
+ mode = bMode
+ sha = bSha
+ conf = 'directory/file'
+
+ if path in currentDirectorySet:
+ cleanMerge = False
+ newPath = uniquePath(path, addBranch)
+ output('CONFLICT (' + conf + '):',
+ 'There is a directory with name', path, 'in',
+ otherBranch + '. Adding', path, 'as', newPath)
+
+ removeFile(False, path)
+ updateFile(False, sha, mode, newPath)
+ else:
+ output('Adding', path)
+ updateFile(True, sha, mode, path)
+
+ elif not oSha and aSha and bSha:
+ #
+ # Case C: Added in both (check for same permissions).
+ #
+ if aSha == bSha:
+ if aMode != bMode:
+ cleanMerge = False
+ output('CONFLICT: File', path,
+ 'added identically in both branches, but permissions',
+ 'conflict', '0%o' % aMode, '->', '0%o' % bMode)
+ output('CONFLICT: adding with permission:', '0%o' % aMode)
+
+ updateFile(False, aSha, aMode, path)
+ else:
+ # This case is handled by git-read-tree
+ assert(False)
+ else:
+ cleanMerge = False
+ newPath1 = uniquePath(path, branch1Name)
+ newPath2 = uniquePath(path, branch2Name)
+ output('CONFLICT (add/add): File', path,
+ 'added non-identically in both branches. Adding as',
+ newPath1, 'and', newPath2, 'instead.')
+ removeFile(False, path)
+ updateFile(False, aSha, aMode, newPath1)
+ updateFile(False, bSha, bMode, newPath2)
+
+ elif oSha and aSha and bSha:
+ #
+ # case D: Modified in both, but differently.
+ #
+ output('Auto-merging', path)
+ [sha, mode, clean, dummy] = \
+ mergeFile(path, oSha, oMode,
+ path, aSha, aMode,
+ path, bSha, bMode,
+ branch1Name, branch2Name)
+ if clean:
+ updateFile(True, sha, mode, path)
+ else:
+ cleanMerge = False
+ output('CONFLICT (content): Merge conflict in', path)
+
+ if cacheOnly:
+ updateFile(False, sha, mode, path)
+ else:
+ updateFileExt(aSha, aMode, path,
+ updateCache=True, updateWd=False)
+ updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
+ else:
+ die("ERROR: Fatal merge failure, shouldn't happen.")
+
+ return cleanMerge
+
+def usage():
+ die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
+
+# main entry point as merge strategy module
+# The first parameters up to -- are merge bases, and the rest are heads.
+# This strategy module figures out merge bases itself, so we only
+# get heads.
+
+if len(sys.argv) < 4:
+ usage()
+
+for nextArg in xrange(1, len(sys.argv)):
+ if sys.argv[nextArg] == '--':
+ if len(sys.argv) != nextArg + 3:
+ die('Not handling anything other than two heads merge.')
+ try:
+ h1 = firstBranch = sys.argv[nextArg + 1]
+ h2 = secondBranch = sys.argv[nextArg + 2]
+ except IndexError:
+ usage()
+ break
+
+print 'Merging', h1, 'with', h2
+
+try:
+ h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
+ h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
+
+ graph = buildGraph([h1, h2])
+
+ [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
+ firstBranch, secondBranch, graph)
+
+ print ''
+except:
+ if isinstance(sys.exc_info()[1], SystemExit):
+ raise
+ else:
+ traceback.print_exc(None, sys.stderr)
+ sys.exit(2)
+
+if clean:
+ sys.exit(0)
+else:
+ sys.exit(1)
diff --git a/git-merge-resolve.sh b/git-merge-resolve.sh
new file mode 100755
index 000000000..966e81ff7
--- /dev/null
+++ b/git-merge-resolve.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+#
+# Resolve two trees, using enhancd multi-base read-tree.
+
+# The first parameters up to -- are merge bases; the rest are heads.
+bases= head= remotes= sep_seen=
+for arg
+do
+ case ",$sep_seen,$head,$arg," in
+ *,--,)
+ sep_seen=yes
+ ;;
+ ,yes,,*)
+ head=$arg
+ ;;
+ ,yes,*)
+ remotes="$remotes$arg "
+ ;;
+ *)
+ bases="$bases$arg "
+ ;;
+ esac
+done
+
+# Give up if we are given more than two remotes -- not handling octopus.
+case "$remotes" in
+?*' '?*)
+ exit 2 ;;
+esac
+
+# Give up if this is a baseless merge.
+if test '' = "$bases"
+then
+ exit 2
+fi
+
+git-update-index --refresh 2>/dev/null
+git-read-tree -u -m $bases $head $remotes || exit 2
+echo "Trying simple merge."
+if result_tree=$(git-write-tree 2>/dev/null)
+then
+ exit 0
+else
+ echo "Simple merge failed, trying Automatic merge."
+ if git-merge-index -o git-merge-one-file -a
+ then
+ exit 0
+ else
+ exit 1
+ fi
+fi
diff --git a/git-merge-stupid.sh b/git-merge-stupid.sh
new file mode 100755
index 000000000..4faecb933
--- /dev/null
+++ b/git-merge-stupid.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+# Resolve two trees, 'stupid merge'.
+
+# The first parameters up to -- are merge bases; the rest are heads.
+bases= head= remotes= sep_seen=
+for arg
+do
+ case ",$sep_seen,$head,$arg," in
+ *,--,)
+ sep_seen=yes
+ ;;
+ ,yes,,*)
+ head=$arg
+ ;;
+ ,yes,*)
+ remotes="$remotes$arg "
+ ;;
+ *)
+ bases="$bases$arg "
+ ;;
+ esac
+done
+
+# Give up if we are given more than two remotes -- not handling octopus.
+case "$remotes" in
+?*' '?*)
+ exit 2 ;;
+esac
+
+# Find an optimum merge base if there are more than one candidates.
+case "$bases" in
+?*' '?*)
+ echo "Trying to find the optimum merge base."
+ G=.tmp-index$$
+ best=
+ best_cnt=-1
+ for c in $bases
+ do
+ rm -f $G
+ GIT_INDEX_FILE=$G git-read-tree -m $c $head $remotes \
+ 2>/dev/null || continue
+ # Count the paths that are unmerged.
+ cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l`
+ if test $best_cnt -le 0 -o $cnt -le $best_cnt
+ then
+ best=$c
+ best_cnt=$cnt
+ if test "$best_cnt" -eq 0
+ then
+ # Cannot do any better than all trivial merge.
+ break
+ fi
+ fi
+ done
+ rm -f $G
+ common="$best"
+ ;;
+*)
+ common="$bases"
+ ;;
+esac
+
+git-update-index --refresh 2>/dev/null
+git-read-tree -u -m $common $head $remotes || exit 2
+echo "Trying simple merge."
+if result_tree=$(git-write-tree 2>/dev/null)
+then
+ exit 0
+else
+ echo "Simple merge failed, trying Automatic merge."
+ if git-merge-index -o git-merge-one-file -a
+ then
+ exit 0
+ else
+ exit 1
+ fi
+fi
diff --git a/git-merge.sh b/git-merge.sh
new file mode 100755
index 000000000..7f481e4ca
--- /dev/null
+++ b/git-merge.sh
@@ -0,0 +1,290 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+. git-sh-setup || die "Not a git archive"
+
+LF='
+'
+
+usage () {
+ die "git-merge [-n] [--no-commit] [-s <strategy>]... <merge-message> <head> <remote>+"
+}
+
+# all_strategies='resolve recursive stupid octopus'
+
+all_strategies='recursive octopus resolve stupid ours'
+default_strategies='resolve octopus'
+use_strategies=
+
+dropsave() {
+ rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
+ "$GIT_DIR/MERGE_SAVE" || exit 1
+}
+
+savestate() {
+ # Stash away any local modifications.
+ git-diff-index -z --name-only $head |
+ cpio -0 -o >"$GIT_DIR/MERGE_SAVE"
+}
+
+restorestate() {
+ if test -f "$GIT_DIR/MERGE_SAVE"
+ then
+ git reset --hard $head
+ cpio -iuv <"$GIT_DIR/MERGE_SAVE"
+ git-update-index --refresh >/dev/null
+ fi
+}
+
+finish () {
+ test '' = "$2" || echo "$2"
+ case "$merge_msg" in
+ '')
+ echo "No merge message -- not updating HEAD"
+ ;;
+ *)
+ git-update-ref HEAD "$1" "$head" || exit 1
+ ;;
+ esac
+
+ case "$no_summary" in
+ '')
+ git-diff-tree -p -M "$head" "$1" |
+ git-apply --stat --summary
+ ;;
+ esac
+}
+
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
+ --no-summa|--no-summar|--no-summary)
+ no_summary=t ;;
+ --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
+ no_commit=t ;;
+ -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
+ --strateg=*|--strategy=*|\
+ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
+ case "$#,$1" in
+ *,*=*)
+ strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+ 1,*)
+ usage ;;
+ *)
+ strategy="$2"
+ shift ;;
+ esac
+ case " $all_strategies " in
+ *" $strategy "*)
+ use_strategies="$use_strategies$strategy " ;;
+ *)
+ die "available strategies are: $all_strategies" ;;
+ esac
+ ;;
+ -*) usage ;;
+ *) break ;;
+ esac
+ shift
+done
+
+case "$use_strategies" in
+'')
+ use_strategies=$default_strategies
+ ;;
+esac
+test "$#" -le 2 && usage ;# we need at least two heads.
+
+merge_msg="$1"
+shift
+head_arg="$1"
+head=$(git-rev-parse --verify "$1"^0) || usage
+shift
+
+# All the rest are remote heads
+for remote
+do
+ git-rev-parse --verify "$remote"^0 >/dev/null ||
+ die "$remote - not something we can merge"
+done
+
+case "$#" in
+1)
+ common=$(git-merge-base --all $head "$@")
+ ;;
+*)
+ common=$(git-show-branch --merge-base $head "$@")
+ ;;
+esac
+echo "$head" >"$GIT_DIR/ORIG_HEAD"
+
+case "$#,$common,$no_commit" in
+*,'',*)
+ # No common ancestors found. We need a real merge.
+ ;;
+1,"$1",*)
+ # If head can reach all the merge then we are up to date.
+ # but first the most common case of merging one remote
+ echo "Already up-to-date."
+ dropsave
+ exit 0
+ ;;
+1,"$head",*)
+ # Again the most common case of merging one remote.
+ echo "Updating from $head to $1."
+ git-update-index --refresh 2>/dev/null
+ new_head=$(git-rev-parse --verify "$1^0") &&
+ git-read-tree -u -m $head "$new_head" &&
+ finish "$new_head" "Fast forward"
+ dropsave
+ exit 0
+ ;;
+1,?*"$LF"?*,*)
+ # We are not doing octopus and not fast forward. Need a
+ # real merge.
+ ;;
+1,*,)
+ # We are not doing octopus, not fast forward, and have only
+ # one common. See if it is really trivial.
+ echo "Trying really trivial in-index merge..."
+ git-update-index --refresh 2>/dev/null
+ if git-read-tree --trivial -m -u $common $head "$1" &&
+ result_tree=$(git-write-tree)
+ then
+ echo "Wonderful."
+ result_commit=$(
+ echo "$merge_msg" |
+ git-commit-tree $result_tree -p HEAD -p "$1"
+ ) || exit
+ finish "$result_commit" "In-index merge"
+ dropsave
+ exit 0
+ fi
+ echo "Nope."
+ ;;
+*)
+ # An octopus. If we can reach all the remote we are up to date.
+ up_to_date=t
+ for remote
+ do
+ common_one=$(git-merge-base --all $head $remote)
+ if test "$common_one" != "$remote"
+ then
+ up_to_date=f
+ break
+ fi
+ done
+ if test "$up_to_date" = t
+ then
+ echo "Already up-to-date. Yeeah!"
+ dropsave
+ exit 0
+ fi
+ ;;
+esac
+
+# At this point, we need a real merge. No matter what strategy
+# we use, it would operate on the index, possibly affecting the
+# working tree, and when resolved cleanly, have the desired tree
+# in the index -- this means that the index must be in sync with
+# the $head commit. The strategies are responsible to ensure this.
+
+case "$use_strategies" in
+?*' '?*)
+ # Stash away the local changes so that we can try more than one.
+ savestate
+ single_strategy=no
+ ;;
+*)
+ rm -f "$GIT_DIR/MERGE_SAVE"
+ single_strategy=yes
+ ;;
+esac
+
+result_tree= best_cnt=-1 best_strategy= wt_strategy=
+for strategy in $use_strategies
+do
+ test "$wt_strategy" = '' || {
+ echo "Rewinding the tree to pristine..."
+ restorestate
+ }
+ case "$single_strategy" in
+ no)
+ echo "Trying merge strategy $strategy..."
+ ;;
+ esac
+
+ # Remember which strategy left the state in the working tree
+ wt_strategy=$strategy
+
+ git-merge-$strategy $common -- "$head_arg" "$@"
+ exit=$?
+ if test "$no_commit" = t && test "$exit" = 0
+ then
+ exit=1 ;# pretend it left conflicts.
+ fi
+
+ test "$exit" = 0 || {
+
+ # The backend exits with 1 when conflicts are left to be resolved,
+ # with 2 when it does not handle the given merge at all.
+
+ if test "$exit" -eq 1
+ then
+ cnt=`{
+ git-diff-files --name-only
+ git-ls-files --unmerged
+ } | wc -l`
+ if test $best_cnt -le 0 -o $cnt -le $best_cnt
+ then
+ best_strategy=$strategy
+ best_cnt=$cnt
+ fi
+ fi
+ continue
+ }
+
+ # Automerge succeeded.
+ result_tree=$(git-write-tree) && break
+done
+
+# If we have a resulting tree, that means the strategy module
+# auto resolved the merge cleanly.
+if test '' != "$result_tree"
+then
+ parents="-p $head"
+ for remote
+ do
+ parents="$parents -p $remote"
+ done
+ result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree $parents) || exit
+ finish "$result_commit" "Merge $result_commit, made by $wt_strategy."
+ dropsave
+ exit 0
+fi
+
+# Pick the result from the best strategy and have the user fix it up.
+case "$best_strategy" in
+'')
+ restorestate
+ die "No merge strategy handled the merge."
+ ;;
+"$wt_strategy")
+ # We already have its result in the working tree.
+ ;;
+*)
+ echo "Rewinding the tree to pristine..."
+ restorestate
+ echo "Using the $best_strategy to prepare resolving by hand."
+ git-merge-$best_strategy $common -- "$head_arg" "$@"
+ ;;
+esac
+for remote
+do
+ echo $remote
+done >"$GIT_DIR/MERGE_HEAD"
+echo $merge_msg >"$GIT_DIR/MERGE_MSG"
+
+die "Automatic merge failed/prevented; fix up by hand"
diff --git a/git-mv.perl b/git-mv.perl
new file mode 100755
index 000000000..a21d87eea
--- /dev/null
+++ b/git-mv.perl
@@ -0,0 +1,197 @@
+#!/usr/bin/perl
+#
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+# Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+
+use warnings;
+use strict;
+use Getopt::Std;
+
+sub usage() {
+ print <<EOT;
+$0 [-f] [-n] <source> <dest>
+$0 [-f] [-k] [-n] <source> ... <dest directory>
+
+In the first form, source must exist and be either a file,
+symlink or directory, dest must not exist. It renames source to dest.
+In the second form, the last argument has to be an existing
+directory; the given sources will be moved into this directory.
+
+Updates the git cache to reflect the change.
+Use "git commit" to make the change permanently.
+
+Options:
+ -f Force renaming/moving, even if target exists
+ -k Continue on error by skipping
+ not-existing or not revision-controlled source
+ -n Do nothing; show what would happen
+EOT
+ exit(1);
+}
+
+# Sanity checks:
+my $GIT_DIR = $ENV{'GIT_DIR'} || ".git";
+
+unless ( -d $GIT_DIR && -d $GIT_DIR . "/objects" &&
+ -d $GIT_DIR . "/objects/" && -d $GIT_DIR . "/refs") {
+ print "Git repository not found.";
+ usage();
+}
+
+
+our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
+getopts("hnfkv") || usage;
+usage() if $opt_h;
+@ARGV >= 1 or usage;
+
+my (@srcArgs, @dstArgs, @srcs, @dsts);
+my ($src, $dst, $base, $dstDir);
+
+my $argCount = scalar @ARGV;
+if (-d $ARGV[$argCount-1]) {
+ $dstDir = $ARGV[$argCount-1];
+ # remove any trailing slash
+ $dstDir =~ s/\/$//;
+ @srcArgs = @ARGV[0..$argCount-2];
+
+ foreach $src (@srcArgs) {
+ $base = $src;
+ $base =~ s/^.*\///;
+ $dst = "$dstDir/". $base;
+ push @dstArgs, $dst;
+ }
+}
+else {
+ if ($argCount != 2) {
+ print "Error: moving to directory '"
+ . $ARGV[$argCount-1]
+ . "' not possible; not exisiting\n";
+ usage;
+ }
+ @srcArgs = ($ARGV[0]);
+ @dstArgs = ($ARGV[1]);
+ $dstDir = "";
+}
+
+my (@allfiles,@srcfiles,@dstfiles);
+my $safesrc;
+my (%overwritten, %srcForDst);
+
+$/ = "\0";
+open(F,"-|","git-ls-files","-z")
+ or die "Failed to open pipe from git-ls-files: " . $!;
+
+@allfiles = map { chomp; $_; } <F>;
+close(F);
+
+
+my ($i, $bad);
+while(scalar @srcArgs > 0) {
+ $src = shift @srcArgs;
+ $dst = shift @dstArgs;
+ $bad = "";
+
+ if ($opt_v) {
+ print "Checking rename of '$src' to '$dst'\n";
+ }
+
+ unless (-f $src || -l $src || -d $src) {
+ $bad = "bad source '$src'";
+ }
+
+ $overwritten{$dst} = 0;
+ if (($bad eq "") && -e $dst) {
+ $bad = "destination '$dst' already exists";
+ if (-f $dst && $opt_f) {
+ print "Warning: $bad; will overwrite!\n";
+ $bad = "";
+ $overwritten{$dst} = 1;
+ }
+ }
+
+ if (($bad eq "") && ($src eq $dstDir)) {
+ $bad = "can not move directory '$src' into itself";
+ }
+
+ if ($bad eq "") {
+ $safesrc = quotemeta($src);
+ @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+ if (scalar @srcfiles == 0) {
+ $bad = "'$src' not under version control";
+ }
+ }
+
+ if ($bad eq "") {
+ if (defined $srcForDst{$dst}) {
+ $bad = "can not move '$src' to '$dst'; already target of ";
+ $bad .= "'".$srcForDst{$dst}."'";
+ }
+ else {
+ $srcForDst{$dst} = $src;
+ }
+ }
+
+ if ($bad ne "") {
+ if ($opt_k) {
+ print "Warning: $bad; skipping\n";
+ next;
+ }
+ print "Error: $bad\n";
+ usage();
+ }
+ push @srcs, $src;
+ push @dsts, $dst;
+}
+
+# Final pass: rename/move
+my (@deletedfiles,@addedfiles,@changedfiles);
+while(scalar @srcs > 0) {
+ $src = shift @srcs;
+ $dst = shift @dsts;
+
+ if ($opt_n || $opt_v) { print "Renaming $src to $dst\n"; }
+ if (!$opt_n) {
+ rename($src,$dst)
+ or die "rename failed: $!";
+ }
+
+ $safesrc = quotemeta($src);
+ @srcfiles = grep /^$safesrc(\/|$)/, @allfiles;
+ @dstfiles = @srcfiles;
+ s/^$safesrc(\/|$)/$dst$1/ for @dstfiles;
+
+ push @deletedfiles, @srcfiles;
+ if (scalar @srcfiles == 1) {
+ if ($overwritten{$dst} ==1) {
+ push @changedfiles, $dst;
+ } else {
+ push @addedfiles, $dst;
+ }
+ }
+ else {
+ push @addedfiles, @dstfiles;
+ }
+}
+
+if ($opt_n) {
+ print "Changed : ". join(", ", @changedfiles) ."\n";
+ print "Adding : ". join(", ", @addedfiles) ."\n";
+ print "Deleting : ". join(", ", @deletedfiles) ."\n";
+ exit(1);
+}
+
+my $rc;
+if (scalar @changedfiles >0) {
+ $rc = system("git-update-index","--",@changedfiles);
+ die "git-update-index failed to update changed files with code $?\n" if $rc;
+}
+if (scalar @addedfiles >0) {
+ $rc = system("git-update-index","--add","--",@addedfiles);
+ die "git-update-index failed to add new names with code $?\n" if $rc;
+}
+$rc = system("git-update-index","--remove","--",@deletedfiles);
+die "git-update-index failed to remove old names with code $?\n" if $rc;
diff --git a/git-octopus.sh b/git-octopus.sh
new file mode 100755
index 000000000..d2471af3c
--- /dev/null
+++ b/git-octopus.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Resolve two or more trees recorded in $GIT_DIR/FETCH_HEAD.
+#
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ die "usage: git octopus"
+}
+
+# Sanity check the heads early.
+while read SHA1 REPO
+do
+ test $(git-cat-file -t $SHA1) = "commit" ||
+ die "$REPO given to octopus is not a commit"
+done <"$GIT_DIR/FETCH_HEAD"
+
+head=$(git-rev-parse --verify HEAD) || exit
+
+git-update-index --refresh ||
+ die "Your working tree is dirty."
+test "$(git-diff-index --cached "$head")" = "" ||
+ die "Your working tree does not match HEAD."
+
+# MRC is the current "merge reference commit"
+# MRT is the current "merge result tree"
+
+MRC=$head PARENT="-p $head"
+MRT=$(git-write-tree)
+CNT=1 ;# counting our head
+NON_FF_MERGE=0
+while read SHA1 REPO
+do
+ common=$(git-merge-base $MRC $SHA1) ||
+ die "Unable to find common commit with $SHA1 from $REPO"
+
+ if test "$common" = $SHA1
+ then
+ echo "Already up-to-date: $REPO"
+ continue
+ fi
+
+ CNT=`expr $CNT + 1`
+ PARENT="$PARENT -p $SHA1"
+
+ if test "$common,$NON_FF_MERGE" = "$MRC,0"
+ then
+ # The first head being merged was a fast-forward.
+ # Advance MRC to the head being merged, and use that
+ # tree as the intermediate result of the merge.
+ # We still need to count this as part of the parent set.
+
+ echo "Fast forwarding to: $REPO"
+ git-read-tree -u -m $head $SHA1 || exit
+ MRC=$SHA1 MRT=$(git-write-tree)
+ continue
+ fi
+
+ NON_FF_MERGE=1
+
+ echo "Trying simple merge with $REPO"
+ git-read-tree -u -m $common $MRT $SHA1 || exit
+ next=$(git-write-tree 2>/dev/null)
+ if test $? -ne 0
+ then
+ echo "Simple merge did not work, trying automatic merge."
+ git-merge-index -o git-merge-one-file -a || {
+ git-read-tree --reset "$head"
+ git-checkout-index -f -q -u -a
+ die "Automatic merge failed; should not be doing Octopus"
+ }
+ next=$(git-write-tree 2>/dev/null)
+ fi
+ MRC=$common
+ MRT=$next
+done <"$GIT_DIR/FETCH_HEAD"
+
+# Just to be careful in case the user feeds nonsense to us.
+case "$CNT" in
+1)
+ echo "No changes."
+ exit 0 ;;
+esac
+result_commit=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD" |
+ git-commit-tree $MRT $PARENT)
+echo "Committed merge $result_commit"
+git-update-ref HEAD $result_commit $head
+git-diff-tree -p $head $result_commit | git-apply --stat
diff --git a/git-parse-remote.sh b/git-parse-remote.sh
new file mode 100755
index 000000000..aea7b0e54
--- /dev/null
+++ b/git-parse-remote.sh
@@ -0,0 +1,189 @@
+#!/bin/sh
+
+. git-sh-setup
+
+get_data_source () {
+ case "$1" in
+ */*)
+ # Not so fast. This could be the partial URL shorthand...
+ token=$(expr "$1" : '\([^/]*\)/')
+ remainder=$(expr "$1" : '[^/]*/\(.*\)')
+ if test -f "$GIT_DIR/branches/$token"
+ then
+ echo branches-partial
+ else
+ echo ''
+ fi
+ ;;
+ *)
+ if test -f "$GIT_DIR/remotes/$1"
+ then
+ echo remotes
+ elif test -f "$GIT_DIR/branches/$1"
+ then
+ echo branches
+ else
+ echo ''
+ fi ;;
+ esac
+}
+
+get_remote_url () {
+ data_source=$(get_data_source "$1")
+ case "$data_source" in
+ '')
+ echo "$1" ;;
+ remotes)
+ sed -ne '/^URL: */{
+ s///p
+ q
+ }' "$GIT_DIR/remotes/$1" ;;
+ branches)
+ sed -e 's/#.*//' "$GIT_DIR/branches/$1" ;;
+ branches-partial)
+ token=$(expr "$1" : '\([^/]*\)/')
+ remainder=$(expr "$1" : '[^/]*/\(.*\)')
+ url=$(sed -e 's/#.*//' "$GIT_DIR/branches/$token")
+ echo "$url/$remainder"
+ ;;
+ *)
+ die "internal error: get-remote-url $1" ;;
+ esac
+}
+
+get_remote_default_refs_for_push () {
+ data_source=$(get_data_source "$1")
+ case "$data_source" in
+ '' | branches | branches-partial)
+ ;; # no default push mapping, just send matching refs.
+ remotes)
+ sed -ne '/^Push: */{
+ s///p
+ }' "$GIT_DIR/remotes/$1" ;;
+ *)
+ die "internal error: get-remote-default-ref-for-push $1" ;;
+ esac
+}
+
+# Subroutine to canonicalize remote:local notation.
+canon_refs_list_for_fetch () {
+ # Leave only the first one alone; add prefix . to the rest
+ # to prevent the secondary branches to be merged by default.
+ dot_prefix=
+ for ref
+ do
+ force=
+ case "$ref" in
+ +*)
+ ref=$(expr "$ref" : '\+\(.*\)')
+ force=+
+ ;;
+ esac
+ expr "$ref" : '.*:' >/dev/null || ref="${ref}:"
+ remote=$(expr "$ref" : '\([^:]*\):')
+ local=$(expr "$ref" : '[^:]*:\(.*\)')
+ case "$remote" in
+ '') remote=HEAD ;;
+ refs/heads/* | refs/tags/*) ;;
+ heads/* | tags/* ) remote="refs/$remote" ;;
+ *) remote="refs/heads/$remote" ;;
+ esac
+ case "$local" in
+ '') local= ;;
+ refs/heads/* | refs/tags/*) ;;
+ heads/* | tags/* ) local="refs/$local" ;;
+ *) local="refs/heads/$local" ;;
+ esac
+
+ if local_ref_name=$(expr "$local" : 'refs/\(.*\)')
+ then
+ git-check-ref-format "$local_ref_name" ||
+ die "* refusing to create funny ref '$local_ref_name' locally"
+ fi
+ echo "${dot_prefix}${force}${remote}:${local}"
+ dot_prefix=.
+ done
+}
+
+# Returns list of src: (no store), or src:dst (store)
+get_remote_default_refs_for_fetch () {
+ data_source=$(get_data_source "$1")
+ case "$data_source" in
+ '' | branches-partial)
+ echo "HEAD:" ;;
+ branches)
+ remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1")
+ case "$remote_branch" in '') remote_branch=master ;; esac
+ echo "refs/heads/${remote_branch}:refs/heads/$1"
+ ;;
+ remotes)
+ # This prefixes the second and later default refspecs
+ # with a '.', to signal git-fetch to mark them
+ # not-for-merge.
+ canon_refs_list_for_fetch $(sed -ne '/^Pull: */{
+ s///p
+ }' "$GIT_DIR/remotes/$1")
+ ;;
+ *)
+ die "internal error: get-remote-default-ref-for-push $1" ;;
+ esac
+}
+
+get_remote_refs_for_push () {
+ case "$#" in
+ 0) die "internal error: get-remote-refs-for-push." ;;
+ 1) get_remote_default_refs_for_push "$@" ;;
+ *) shift; echo "$@" ;;
+ esac
+}
+
+get_remote_refs_for_fetch () {
+ case "$#" in
+ 0)
+ die "internal error: get-remote-refs-for-fetch." ;;
+ 1)
+ get_remote_default_refs_for_fetch "$@" ;;
+ *)
+ shift
+ tag_just_seen=
+ for ref
+ do
+ if test "$tag_just_seen"
+ then
+ echo "refs/tags/${ref}:refs/tags/${ref}"
+ tag_just_seen=
+ continue
+ else
+ case "$ref" in
+ tag)
+ tag_just_seen=yes
+ continue
+ ;;
+ esac
+ fi
+ canon_refs_list_for_fetch "$ref"
+ done
+ ;;
+ esac
+}
+
+resolve_alternates () {
+ # original URL (xxx.git)
+ top_=`expr "$1" : '\([^:]*:/*[^/]*\)/'`
+ while read path
+ do
+ case "$path" in
+ \#* | '')
+ continue ;;
+ /*)
+ echo "$top_$path/" ;;
+ ../*)
+ # relative -- ugly but seems to work.
+ echo "$1/objects/$path/" ;;
+ *)
+ # exit code may not be caught by the reader.
+ echo "bad alternate: $path"
+ exit 1 ;;
+ esac
+ done
+}
diff --git a/git-prune.sh b/git-prune.sh
new file mode 100755
index 000000000..c4de7f5f2
--- /dev/null
+++ b/git-prune.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+. git-sh-setup || die "Not a git archive"
+
+dryrun=
+echo=
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -n) dryrun=-n echo=echo ;;
+ --) break ;;
+ -*) echo >&2 "usage: git-prune [ -n ] [ heads... ]"; exit 1 ;;
+ *) break ;;
+ esac
+ shift;
+done
+
+sync
+git-fsck-objects --full --cache --unreachable "$@" |
+sed -ne '/unreachable /{
+ s/unreachable [^ ][^ ]* //
+ s|\(..\)|\1/|p
+}' | {
+ cd "$GIT_OBJECT_DIRECTORY" || exit
+ xargs $echo rm -f
+ rmdir 2>/dev/null [0-9a-f][0-9a-f]
+}
+
+git-prune-packed $dryrun
+
+redundant=$(git-pack-redundant --all)
+if test "" != "$redundant"
+then
+ if test "" = "$dryrun"
+ then
+ echo "$redundant" | xargs rm -f
+ else
+ echo rm -f "$redundant"
+ fi
+fi
diff --git a/git-pull.sh b/git-pull.sh
new file mode 100755
index 000000000..3b875ad43
--- /dev/null
+++ b/git-pull.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+# Fetch one or more remote refs and merge it/them into the current HEAD.
+
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ echo >&2 "usage: $0"' [-n] [--no-commit] [--no-summary] [--help]
+ [-s strategy]...
+ [<fetch-options>]
+ <repo> <head>...
+
+Fetch one or more remote refs and merge it/them into the current HEAD.
+'
+ exit 1
+}
+
+strategy_args= no_summary= no_commit=
+while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac
+do
+ case "$1" in
+ -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
+ --no-summa|--no-summar|--no-summary)
+ no_summary=-n ;;
+ --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
+ no_commit=--no-commit ;;
+ -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
+ --strateg=*|--strategy=*|\
+ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
+ case "$#,$1" in
+ *,*=*)
+ strategy=`expr "$1" : '-[^=]*=\(.*\)'` ;;
+ 1,*)
+ usage ;;
+ *)
+ strategy="$2"
+ shift ;;
+ esac
+ strategy_args="${strategy_args}-s $strategy "
+ ;;
+ -h|--h|--he|--hel|--help)
+ usage
+ ;;
+ -*)
+ # Pass thru anything that is meant for fetch.
+ break
+ ;;
+ esac
+ shift
+done
+
+orig_head=$(git-rev-parse --verify HEAD) || die "Pulling into a black hole?"
+git-fetch --update-head-ok "$@" || exit 1
+
+curr_head=$(git-rev-parse --verify HEAD)
+if test "$curr_head" != "$orig_head"
+then
+ # The fetch involved updating the current branch.
+
+ # The working tree and the index file is still based on the
+ # $orig_head commit, but we are merging into $curr_head.
+ # First update the working tree to match $curr_head.
+
+ echo >&2 "Warning: fetch updated the current branch head."
+ echo >&2 "Warning: fast forwarding your working tree."
+ git-read-tree -u -m "$orig_head" "$curr_head" ||
+ die "You need to first update your working tree."
+fi
+
+merge_head=$(sed -e '/ not-for-merge /d' \
+ -e 's/ .*//' "$GIT_DIR"/FETCH_HEAD | \
+ tr '\012' ' ')
+
+case "$merge_head" in
+'')
+ echo >&2 "No changes."
+ exit 0
+ ;;
+?*' '?*)
+ var=`git-var -l | sed -ne 's/^pull\.octopus=/-s /p'`
+ if test '' = "$var"
+ then
+ strategy_default_args='-s octopus'
+ else
+ strategy_default_args=$var
+ fi
+ ;;
+*)
+ var=`git-var -l | sed -ne 's/^pull\.twohead=/-s /p'`
+ if test '' = "$var"
+ then
+ strategy_default_args='-s recursive'
+ else
+ strategy_default_args=$var
+ fi
+ ;;
+esac
+
+case "$strategy_args" in
+'')
+ strategy_args=$strategy_default_args
+ ;;
+esac
+
+merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD")
+git-merge $no_summary $no_commit $strategy_args "$merge_name" HEAD $merge_head
diff --git a/git-push.sh b/git-push.sh
new file mode 100755
index 000000000..edc0b8317
--- /dev/null
+++ b/git-push.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ die "Usage: git push [--all] [--force] <repository> [<refspec>]"
+}
+
+
+# Parse out parameters and then stop at remote, so that we can
+# translate it using .git/branches information
+has_all=
+has_force=
+has_exec=
+remote=
+
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ --all)
+ has_all=--all ;;
+ --force)
+ has_force=--force ;;
+ --exec=*)
+ has_exec="$1" ;;
+ -*)
+ usage ;;
+ *)
+ set x "$@"
+ shift
+ break ;;
+ esac
+ shift
+done
+case "$#" in
+0)
+ echo "Where would you want to push today?"
+ usage ;;
+esac
+
+. git-parse-remote
+remote=$(get_remote_url "$@")
+case "$has_all" in
+--all) set x ;;
+'') set x $(get_remote_refs_for_push "$@") ;;
+esac
+shift
+
+case "$remote" in
+git://*)
+ die "Cannot use READ-ONLY transport to push to $remote" ;;
+rsync://*)
+ die "Pushing with rsync transport is deprecated" ;;
+esac
+
+set x "$remote" "$@"; shift
+test "$has_all" && set x "$has_all" "$@" && shift
+test "$has_force" && set x "$has_force" "$@" && shift
+test "$has_exec" && set x "$has_exec" "$@" && shift
+
+case "$remote" in
+http://* | https://*)
+ exec git-http-push "$@";;
+*)
+ exec git-send-pack "$@";;
+esac
diff --git a/git-rebase.sh b/git-rebase.sh
new file mode 100755
index 000000000..528976288
--- /dev/null
+++ b/git-rebase.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano.
+#
+
+. git-sh-setup || die "Not a git archive."
+
+# The other head is given
+other=$(git-rev-parse --verify "$1^0") || exit
+
+# The tree must be really really clean.
+git-update-index --refresh || exit
+diff=$(git-diff-index --cached --name-status -r HEAD)
+case "$different" in
+?*) echo "$diff"
+ exit 1
+ ;;
+esac
+
+# If the branch to rebase is given, first switch to it.
+case "$#" in
+2)
+ git-checkout "$2" || exit
+esac
+
+# Rewind the head to "$other"
+git-reset --hard "$other"
+git-format-patch -k --stdout --full-index "$other" ORIG_HEAD |
+git am --binary -3 -k
diff --git a/git-relink.perl b/git-relink.perl
new file mode 100755
index 000000000..f6b4f6a2f
--- /dev/null
+++ b/git-relink.perl
@@ -0,0 +1,173 @@
+#!/usr/bin/env perl
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+# Distribution permitted under the GPL v2, as distributed
+# by the Free Software Foundation.
+# Later versions of the GPL at the discretion of Linus Torvalds
+#
+# Scan two git object-trees, and hardlink any common objects between them.
+
+use 5.006;
+use strict;
+use warnings;
+use Getopt::Long;
+
+sub get_canonical_form($);
+sub do_scan_directory($$$);
+sub compare_two_files($$);
+sub usage();
+sub link_two_files($$);
+
+# stats
+my $total_linked = 0;
+my $total_already = 0;
+my ($linked,$already);
+
+my $fail_on_different_sizes = 0;
+my $help = 0;
+GetOptions("safe" => \$fail_on_different_sizes,
+ "help" => \$help);
+
+usage() if $help;
+
+my (@dirs) = @ARGV;
+
+usage() if (!defined $dirs[0] || !defined $dirs[1]);
+
+$_ = get_canonical_form($_) foreach (@dirs);
+
+my $master_dir = pop @dirs;
+
+opendir(D,$master_dir . "objects/")
+ or die "Failed to open $master_dir/objects/ : $!";
+
+my @hashdirs = grep !/^\.{1,2}$/, readdir(D);
+
+foreach my $repo (@dirs) {
+ $linked = 0;
+ $already = 0;
+ printf("Searching '%s' and '%s' for common objects and hardlinking them...\n",
+ $master_dir,$repo);
+
+ foreach my $hashdir (@hashdirs) {
+ do_scan_directory($master_dir, $hashdir, $repo);
+ }
+
+ printf("Linked %d files, %d were already linked.\n",$linked, $already);
+
+ $total_linked += $linked;
+ $total_already += $already;
+}
+
+printf("Totals: Linked %d files, %d were already linked.\n",
+ $total_linked, $total_already);
+
+
+sub do_scan_directory($$$) {
+ my ($srcdir, $subdir, $dstdir) = @_;
+
+ my $sfulldir = sprintf("%sobjects/%s/",$srcdir,$subdir);
+ my $dfulldir = sprintf("%sobjects/%s/",$dstdir,$subdir);
+
+ opendir(S,$sfulldir)
+ or die "Failed to opendir $sfulldir: $!";
+
+ foreach my $file (grep(!/\.{1,2}$/, readdir(S))) {
+ my $sfilename = $sfulldir . $file;
+ my $dfilename = $dfulldir . $file;
+
+ compare_two_files($sfilename,$dfilename);
+
+ }
+ closedir(S);
+}
+
+sub compare_two_files($$) {
+ my ($sfilename, $dfilename) = @_;
+
+ # Perl's stat returns relevant information as follows:
+ # 0 = dev number
+ # 1 = inode number
+ # 7 = size
+ my @sstatinfo = stat($sfilename);
+ my @dstatinfo = stat($dfilename);
+
+ if (@sstatinfo == 0 && @dstatinfo == 0) {
+ die sprintf("Stat of both %s and %s failed: %s\n",$sfilename, $dfilename, $!);
+
+ } elsif (@dstatinfo == 0) {
+ return;
+ }
+
+ if ( ($sstatinfo[0] == $dstatinfo[0]) &&
+ ($sstatinfo[1] != $dstatinfo[1])) {
+ if ($sstatinfo[7] == $dstatinfo[7]) {
+ link_two_files($sfilename, $dfilename);
+
+ } else {
+ my $err = sprintf("ERROR: File sizes are not the same, cannot relink %s to %s.\n",
+ $sfilename, $dfilename);
+ if ($fail_on_different_sizes) {
+ die $err;
+ } else {
+ warn $err;
+ }
+ }
+
+ } elsif ( ($sstatinfo[0] == $dstatinfo[0]) &&
+ ($sstatinfo[1] == $dstatinfo[1])) {
+ $already++;
+ }
+}
+
+sub get_canonical_form($) {
+ my $dir = shift;
+ my $original = $dir;
+
+ die "$dir is not a directory." unless -d $dir;
+
+ $dir .= "/" unless $dir =~ m#/$#;
+ $dir .= ".git/" unless $dir =~ m#\.git/$#;
+
+ die "$original does not have a .git/ subdirectory.\n" unless -d $dir;
+
+ return $dir;
+}
+
+sub link_two_files($$) {
+ my ($sfilename, $dfilename) = @_;
+ my $tmpdname = sprintf("%s.old",$dfilename);
+ rename($dfilename,$tmpdname)
+ or die sprintf("Failure renaming %s to %s: %s",
+ $dfilename, $tmpdname, $!);
+
+ if (! link($sfilename,$dfilename)) {
+ my $failtxt = "";
+ unless (rename($tmpdname,$dfilename)) {
+ $failtxt = sprintf(
+ "Git Repository containing %s is probably corrupted, " .
+ "please copy '%s' to '%s' to fix.\n",
+ $tmpdname, $dfilename);
+ }
+
+ die sprintf("Failed to link %s to %s: %s\n%s" .
+ $sfilename, $dfilename,
+ $!, $dfilename, $failtxt);
+ }
+
+ unlink($tmpdname)
+ or die sprintf("Unlink of %s failed: %s\n",
+ $dfilename, $!);
+
+ $linked++;
+}
+
+
+sub usage() {
+ print("Usage: $0 [--safe] <dir> [<dir> ...] <master_dir> \n");
+ print("All directories should contain a .git/objects/ subdirectory.\n");
+ print("Options\n");
+ print("\t--safe\t" .
+ "Stops if two objects with the same hash exist but " .
+ "have different sizes. Default is to warn and continue.\n");
+ exit(1);
+}
diff --git a/git-repack.sh b/git-repack.sh
new file mode 100755
index 000000000..55a7b27dc
--- /dev/null
+++ b/git-repack.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+
+. git-sh-setup || die "Not a git archive"
+
+no_update_info= all_into_one= remove_redundant= local=
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -n) no_update_info=t ;;
+ -a) all_into_one=t ;;
+ -d) remove_redundant=t ;;
+ -l) local=t ;;
+ *) break ;;
+ esac
+ shift
+done
+
+rm -f .tmp-pack-*
+PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
+
+# There will be more repacking strategies to come...
+case ",$all_into_one," in
+,,)
+ rev_list='--unpacked'
+ rev_parse='--all'
+ pack_objects='--incremental'
+ ;;
+,t,)
+ rev_list=
+ rev_parse='--all'
+ pack_objects=
+ ;;
+esac
+if [ "$local" ]; then
+ pack_objects="$pack_objects --local"
+fi
+name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) |
+ git-pack-objects --non-empty $pack_objects .tmp-pack) ||
+ exit 1
+if [ -z "$name" ]; then
+ echo Nothing new to pack.
+ if test "$remove_redundant" = t ; then
+ echo "Removing redundant packs."
+ sync
+ redundant=$(git-pack-redundant --all)
+ if test "$redundant" != "" ; then
+ echo $redundant | xargs rm
+ fi
+ fi
+ exit 0
+fi
+echo "Pack pack-$name created."
+
+mkdir -p "$PACKDIR" || exit
+
+mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
+mv .tmp-pack-$name.idx "$PACKDIR/pack-$name.idx" ||
+exit
+
+if test "$remove_redundant" = t
+then
+ sync
+ if test "$all_into_one" = t
+ then
+ cd "$PACKDIR"
+ existing=`find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
+ for e in $existing
+ do
+ case "$e" in
+ ./pack-$name.pack | ./pack-$name.idx) ;;
+ *) rm -f $e ;;
+ esac
+ done
+ else
+ redundant=$(git-pack-redundant --all)
+ if test "$redundant" != "" ; then
+ echo $redundant | xargs rm
+ fi
+ fi
+fi
+
+case "$no_update_info" in
+t) : ;;
+*) git-update-server-info ;;
+esac
diff --git a/git-request-pull.sh b/git-request-pull.sh
new file mode 100755
index 000000000..ae6cd272b
--- /dev/null
+++ b/git-request-pull.sh
@@ -0,0 +1,35 @@
+#!/bin/sh -e
+# Copyright 2005, Ryan Anderson <ryan@michonline.com>
+#
+# This file is licensed under the GPL v2, or a later version
+# at the discretion of Linus Torvalds.
+
+usage()
+{
+ echo "$0 <commit> <url> [ <head> ]"
+ echo " Summarizes the changes since <commit> to the standard output,"
+ echo " and includes <url> in the message generated."
+ exit 1
+}
+
+revision=$1
+url=$2
+head=${3-HEAD}
+
+[ "$revision" ] || usage
+[ "$url" ] || usage
+
+baserev=`git-rev-parse --verify "$revision"^0` &&
+headrev=`git-rev-parse --verify "$head"^0` || exit
+
+echo "The following changes since commit $baserev:"
+git log --max-count=1 --pretty=short "$baserev" |
+git-shortlog | sed -e 's/^\(.\)/ \1/'
+
+echo "are found in the git repository at:"
+echo
+echo " $url"
+echo
+
+git log $baserev..$headrev | git-shortlog ;
+git diff $baserev..$headrev | git-apply --stat --summary
diff --git a/git-reset.sh b/git-reset.sh
new file mode 100755
index 000000000..2086d26d3
--- /dev/null
+++ b/git-reset.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ die 'Usage: git reset [--mixed | --soft | --hard] [<commit-ish>]'
+}
+
+tmp=/var/tmp/reset.$$
+trap 'rm -f $tmp-*' 0 1 2 3 15
+
+reset_type=--mixed
+case "$1" in
+--mixed | --soft | --hard)
+ reset_type="$1"
+ shift
+ ;;
+-*)
+ usage ;;
+esac
+
+rev=$(git-rev-parse --verify --default HEAD "$@") || exit
+rev=$(git-rev-parse --verify $rev^0) || exit
+
+# We need to remember the set of paths that _could_ be left
+# behind before a hard reset, so that we can remove them.
+if test "$reset_type" = "--hard"
+then
+ {
+ git-ls-files --stage -z
+ git-rev-parse --verify HEAD 2>/dev/null &&
+ git-ls-tree -r -z HEAD
+ } | perl -e '
+ use strict;
+ my %seen;
+ $/ = "\0";
+ while (<>) {
+ chomp;
+ my ($info, $path) = split(/\t/, $_);
+ next if ($info =~ / tree /);
+ if (!$seen{$path}) {
+ $seen{$path} = 1;
+ print "$path\0";
+ }
+ }
+ ' >$tmp-exists
+fi
+
+# Soft reset does not touch the index file nor the working tree
+# at all, but requires them in a good order. Other resets reset
+# the index file to the tree object we are switching to.
+if test "$reset_type" = "--soft"
+then
+ if test -f "$GIT_DIR/MERGE_HEAD" ||
+ test "" != "$(git-ls-files --unmerged)"
+ then
+ die "Cannot do a soft reset in the middle of a merge."
+ fi
+else
+ git-read-tree --reset "$rev" || exit
+fi
+
+# Any resets update HEAD to the head being switched to.
+if orig=$(git-rev-parse --verify HEAD 2>/dev/null)
+then
+ echo "$orig" >"$GIT_DIR/ORIG_HEAD"
+else
+ rm -f "$GIT_DIR/ORIG_HEAD"
+fi
+git-update-ref HEAD "$rev"
+
+case "$reset_type" in
+--hard )
+ # Hard reset matches the working tree to that of the tree
+ # being switched to.
+ git-checkout-index -f -u -q -a
+ git-ls-files --cached -z |
+ perl -e '
+ use strict;
+ my (%keep, $fh);
+ $/ = "\0";
+ while (<STDIN>) {
+ chomp;
+ $keep{$_} = 1;
+ }
+ open $fh, "<", $ARGV[0]
+ or die "cannot open $ARGV[0]";
+ while (<$fh>) {
+ chomp;
+ if (! exists $keep{$_}) {
+ # it is ok if this fails -- it may already
+ # have been culled by checkout-index.
+ unlink $_;
+ }
+ }
+ ' $tmp-exists
+ ;;
+--soft )
+ ;; # Nothing else to do
+--mixed )
+ # Report what has not been updated.
+ git-update-index --refresh
+ ;;
+esac
+
+rm -f "$GIT_DIR/MERGE_HEAD"
diff --git a/git-resolve.sh b/git-resolve.sh
new file mode 100755
index 000000000..7d8fb54f9
--- /dev/null
+++ b/git-resolve.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+# Resolve two trees.
+#
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ die "git-resolve <head> <remote> <merge-message>"
+}
+
+dropheads() {
+ rm -f -- "$GIT_DIR/MERGE_HEAD" \
+ "$GIT_DIR/LAST_MERGE" || exit 1
+}
+
+head=$(git-rev-parse --verify "$1"^0) &&
+merge=$(git-rev-parse --verify "$2"^0) &&
+merge_msg="$3" || usage
+
+#
+# The remote name is just used for the message,
+# but we do want it.
+#
+if [ -z "$head" -o -z "$merge" -o -z "$merge_msg" ]; then
+ usage
+fi
+
+dropheads
+echo $head > "$GIT_DIR"/ORIG_HEAD
+echo $merge > "$GIT_DIR"/LAST_MERGE
+
+common=$(git-merge-base $head $merge)
+if [ -z "$common" ]; then
+ die "Unable to find common commit between" $merge $head
+fi
+
+case "$common" in
+"$merge")
+ echo "Already up-to-date. Yeeah!"
+ dropheads
+ exit 0
+ ;;
+"$head")
+ echo "Updating from $head to $merge."
+ git-read-tree -u -m $head $merge || exit 1
+ git-update-ref HEAD "$merge" "$head"
+ git-diff-tree -p $head $merge | git-apply --stat
+ dropheads
+ exit 0
+ ;;
+esac
+
+# Find an optimum merge base if there are more than one candidates.
+LF='
+'
+common=$(git-merge-base -a $head $merge)
+case "$common" in
+?*"$LF"?*)
+ echo "Trying to find the optimum merge base."
+ G=.tmp-index$$
+ best=
+ best_cnt=-1
+ for c in $common
+ do
+ rm -f $G
+ GIT_INDEX_FILE=$G git-read-tree -m $c $head $merge \
+ 2>/dev/null || continue
+ # Count the paths that are unmerged.
+ cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l`
+ if test $best_cnt -le 0 -o $cnt -le $best_cnt
+ then
+ best=$c
+ best_cnt=$cnt
+ if test "$best_cnt" -eq 0
+ then
+ # Cannot do any better than all trivial merge.
+ break
+ fi
+ fi
+ done
+ rm -f $G
+ common="$best"
+esac
+
+echo "Trying to merge $merge into $head using $common."
+git-update-index --refresh 2>/dev/null
+git-read-tree -u -m $common $head $merge || exit 1
+result_tree=$(git-write-tree 2> /dev/null)
+if [ $? -ne 0 ]; then
+ echo "Simple merge failed, trying Automatic merge"
+ git-merge-index -o git-merge-one-file -a
+ if [ $? -ne 0 ]; then
+ echo $merge > "$GIT_DIR"/MERGE_HEAD
+ die "Automatic merge failed, fix up by hand"
+ fi
+ result_tree=$(git-write-tree) || exit 1
+fi
+result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree -p $head -p $merge)
+echo "Committed merge $result_commit"
+git-update-ref HEAD "$result_commit" "$head"
+git-diff-tree -p $head $result_commit | git-apply --stat
+dropheads
diff --git a/git-revert.sh b/git-revert.sh
new file mode 100755
index 000000000..4154fe0d1
--- /dev/null
+++ b/git-revert.sh
@@ -0,0 +1,169 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+#
+. git-sh-setup || die "Not a git archive"
+
+case "$0" in
+*-revert* )
+ me=revert ;;
+*-cherry-pick* )
+ me=cherry-pick ;;
+* )
+ die "What are ou talking about?" ;;
+esac
+
+usage () {
+ case "$me" in
+ cherry-pick)
+ die "usage git $me [-n] [-r] <commit-ish>"
+ ;;
+ revert)
+ die "usage git $me [-n] <commit-ish>"
+ ;;
+ esac
+}
+
+no_commit= replay=
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
+ --no-commi|--no-commit)
+ no_commit=t
+ ;;
+ -r|--r|--re|--rep|--repl|--repla|--replay)
+ replay=t
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+test "$me,$replay" = "revert,t" && usage
+
+case "$no_commit" in
+t)
+ # We do not intend to commit immediately. We just want to
+ # merge the differences in.
+ head=$(git-write-tree) ||
+ die "Your index file is unmerged."
+ ;;
+*)
+ head=$(git-rev-parse --verify HEAD) ||
+ die "You do not have a valid HEAD"
+ files=$(git-diff-index --cached --name-only $head) || exit
+ if [ "$files" ]; then
+ die "Dirty index: cannot $me (dirty: $files)"
+ fi
+ ;;
+esac
+
+rev=$(git-rev-parse --verify "$@") &&
+commit=$(git-rev-parse --verify "$rev^0") ||
+ die "Not a single commit $@"
+prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
+ die "Cannot run $me a root commit"
+git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
+ die "Cannot run $me a multi-parent commit."
+
+# "commit" is an existing commit. We would want to apply
+# the difference it introduces since its first parent "prev"
+# on top of the current HEAD if we are cherry-pick. Or the
+# reverse of it if we are revert.
+
+case "$me" in
+revert)
+ git-rev-list --pretty=oneline --max-count=1 $commit |
+ sed -e '
+ s/^[^ ]* /Revert "/
+ s/$/"/'
+ echo
+ echo "This reverts $commit commit."
+ test "$rev" = "$commit" ||
+ echo "(original 'git revert' arguments: $@)"
+ base=$commit next=$prev
+ ;;
+
+cherry-pick)
+ pick_author_script='
+ /^author /{
+ h
+ s/^author \([^<]*\) <[^>]*> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+ g
+ s/^author [^<]* <\([^>]*\)> .*$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+ g
+ s/^author [^<]* <[^>]*> \(.*\)$/\1/
+ s/'\''/'\''\'\'\''/g
+ s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+ q
+ }'
+ set_author_env=`git-cat-file commit "$commit" |
+ LANG=C LC_ALL=C sed -ne "$pick_author_script"`
+ eval "$set_author_env"
+ export GIT_AUTHOR_NAME
+ export GIT_AUTHOR_EMAIL
+ export GIT_AUTHOR_DATE
+
+ git-cat-file commit $commit | sed -e '1,/^$/d'
+ case "$replay" in
+ '')
+ echo "(cherry picked from $commit commit)"
+ test "$rev" = "$commit" ||
+ echo "(original 'git cherry-pick' arguments: $@)"
+ ;;
+ esac
+ base=$prev next=$commit
+ ;;
+
+esac >.msg
+
+# This three way merge is an interesting one. We are at
+# $head, and would want to apply the change between $commit
+# and $prev on top of us (when reverting), or the change between
+# $prev and $commit on top of us (when cherry-picking or replaying).
+
+echo >&2 "First trying simple merge strategy to $me."
+git-read-tree -m -u $base $head $next &&
+result=$(git-write-tree 2>/dev/null) || {
+ echo >&2 "Simple $me fails; trying Automatic $me."
+ git-merge-index -o git-merge-one-file -a || {
+ echo >&2 "Automatic $me failed. After fixing it up,"
+ echo >&2 "you can use \"git commit -F .msg\""
+ case "$me" in
+ cherry-pick)
+ echo >&2 "You may choose to use the following when making"
+ echo >&2 "the commit:"
+ echo >&2 "$set_author_env"
+ esac
+ exit 1
+ }
+ result=$(git-write-tree) || exit
+}
+echo >&2 "Finished one $me."
+
+# If we are cherry-pick, and if the merge did not result in
+# hand-editing, we will hit this commit and inherit the original
+# author date and name.
+# If we are revert, or if our cherry-pick results in a hand merge,
+# we had better say that the current user is responsible for that.
+
+case "$no_commit" in
+'')
+ git-commit -n -F .msg
+ rm -f .msg
+ ;;
+esac
diff --git a/git-send-email.perl b/git-send-email.perl
new file mode 100755
index 000000000..ec1428d96
--- /dev/null
+++ b/git-send-email.perl
@@ -0,0 +1,368 @@
+#!/usr/bin/perl -w
+#
+# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>
+# Copyright 2005 Ryan Anderson <ryan@michonline.com>
+#
+# GPL v2 (See COPYING)
+#
+# Ported to support git "mbox" format files by Ryan Anderson <ryan@michonline.com>
+#
+# Sends a collection of emails to the given email addresses, disturbingly fast.
+#
+# Supports two formats:
+# 1. mbox format files (ignoring most headers and MIME formatting - this is designed for sending patches)
+# 2. The original format support by Greg's script:
+# first line of the message is who to CC,
+# and second line is the subject of the message.
+#
+
+use strict;
+use warnings;
+use Term::ReadLine;
+use Mail::Sendmail qw(sendmail %mailcfg);
+use Getopt::Long;
+use Data::Dumper;
+use Email::Valid;
+
+sub unique_email_list(@);
+sub cleanup_compose_files();
+
+# Constants (essentially)
+my $compose_filename = ".msg.$$";
+
+# Variables we fill in automatically, or via prompting:
+my (@to,@cc,$initial_reply_to,$initial_subject,@files,$from,$compose);
+
+# Behavior modification variables
+my ($chain_reply_to, $smtp_server) = (1, "localhost");
+
+# Example reply to:
+#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
+
+my $term = new Term::ReadLine 'git-send-email';
+
+# Begin by accumulating all the variables (defined above), that we will end up
+# needing, first, from the command line:
+
+my $rc = GetOptions("from=s" => \$from,
+ "in-reply-to=s" => \$initial_reply_to,
+ "subject=s" => \$initial_subject,
+ "to=s" => \@to,
+ "chain-reply-to!" => \$chain_reply_to,
+ "smtp-server=s" => \$smtp_server,
+ "compose" => \$compose,
+ );
+
+# Now, let's fill any that aren't set in with defaults:
+
+open(GITVAR,"-|","git-var","-l")
+ or die "Failed to open pipe from git-var: $!";
+
+my ($author,$committer);
+while(<GITVAR>) {
+ chomp;
+ my ($var,$data) = split /=/,$_,2;
+ my @fields = split /\s+/, $data;
+
+ my $ident = join(" ", @fields[0...(@fields-3)]);
+
+ if ($var eq 'GIT_AUTHOR_IDENT') {
+ $author = $ident;
+ } elsif ($var eq 'GIT_COMMITTER_IDENT') {
+ $committer = $ident;
+ }
+}
+close(GITVAR);
+
+my $prompting = 0;
+if (!defined $from) {
+ $from = $author || $committer;
+ do {
+ $_ = $term->readline("Who should the emails appear to be from? ",
+ $from);
+ } while (!defined $_);
+
+ $from = $_;
+ print "Emails will be sent from: ", $from, "\n";
+ $prompting++;
+}
+
+if (!@to) {
+ do {
+ $_ = $term->readline("Who should the emails be sent to? ",
+ "");
+ } while (!defined $_);
+ my $to = $_;
+ push @to, split /,/, $to;
+ $prompting++;
+}
+
+if (!defined $initial_subject && $compose) {
+ do {
+ $_ = $term->readline("What subject should the emails start with? ",
+ $initial_subject);
+ } while (!defined $_);
+ $initial_subject = $_;
+ $prompting++;
+}
+
+if (!defined $initial_reply_to && $prompting) {
+ do {
+ $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
+ $initial_reply_to);
+ } while (!defined $_);
+
+ $initial_reply_to = $_;
+ $initial_reply_to =~ s/(^\s+|\s+$)//g;
+}
+
+if (!defined $smtp_server) {
+ $smtp_server = "localhost";
+}
+
+if ($compose) {
+ # Note that this does not need to be secure, but we will make a small
+ # effort to have it be unique
+ open(C,">",$compose_filename)
+ or die "Failed to open for writing $compose_filename: $!";
+ print C "From \n";
+ printf C "Subject: %s\n\n", $initial_subject;
+ printf C <<EOT;
+GIT: Please enter your email below.
+GIT: Lines beginning in "GIT: " will be removed.
+GIT: Consider including an overall diffstat or table of contents
+GIT: for the patch you are writing.
+
+EOT
+ close(C);
+
+ my $editor = $ENV{EDITOR};
+ $editor = 'vi' unless defined $editor;
+ system($editor, $compose_filename);
+
+ open(C2,">",$compose_filename . ".final")
+ or die "Failed to open $compose_filename.final : " . $!;
+
+ open(C,"<",$compose_filename)
+ or die "Failed to open $compose_filename : " . $!;
+
+ while(<C>) {
+ next if m/^GIT: /;
+ print C2 $_;
+ }
+ close(C);
+ close(C2);
+
+ do {
+ $_ = $term->readline("Send this email? (y|n) ");
+ } while (!defined $_);
+
+ if (uc substr($_,0,1) ne 'Y') {
+ cleanup_compose_files();
+ exit(0);
+ }
+
+ @files = ($compose_filename . ".final");
+}
+
+
+# Now that all the defaults are set, process the rest of the command line
+# arguments and collect up the files that need to be processed.
+for my $f (@ARGV) {
+ if (-d $f) {
+ opendir(DH,$f)
+ or die "Failed to opendir $f: $!";
+
+ push @files, grep { -f $_ } map { +$f . "/" . $_ }
+ sort readdir(DH);
+
+ } elsif (-f $f) {
+ push @files, $f;
+
+ } else {
+ print STDERR "Skipping $f - not found.\n";
+ }
+}
+
+if (@files) {
+ print $_,"\n" for @files;
+} else {
+ print <<EOT;
+git-send-email [options] <file | directory> [... file | directory ]
+Options:
+ --from Specify the "From:" line of the email to be sent.
+
+ --to Specify the primary "To:" line of the email.
+
+ --compose Use \$EDITOR to edit an introductory message for the
+ patch series.
+
+ --subject Specify the initial "Subject:" line.
+ Only necessary if --compose is also set. If --compose
+ is not set, this will be prompted for.
+
+ --in-reply-to Specify the first "In-Reply-To:" header line.
+ Only used if --compose is also set. If --compose is not
+ set, this will be prompted for.
+
+ --chain-reply-to If set, the replies will all be to the previous
+ email sent, rather than to the first email sent.
+ Defaults to on.
+
+ --smtp-server If set, specifies the outgoing SMTP server to use.
+ Defaults to localhost.
+
+Error: Please specify a file or a directory on the command line.
+EOT
+ exit(1);
+}
+
+# Variables we set as part of the loop over files
+our ($message_id, $cc, %mail, $subject, $reply_to, $message);
+
+
+# Usually don't need to change anything below here.
+
+# we make a "fake" message id by taking the current number
+# of seconds since the beginning of Unix time and tacking on
+# a random number to the end, in case we are called quicker than
+# 1 second since the last time we were called.
+
+# We'll setup a template for the message id, using the "from" address:
+my $message_id_from = Email::Valid->address($from);
+my $message_id_template = "<%s-git-send-email-$message_id_from>";
+
+sub make_message_id
+{
+ my $date = `date "+\%s"`;
+ chomp($date);
+ my $pseudo_rand = int (rand(4200));
+ $message_id = sprintf $message_id_template, "$date$pseudo_rand";
+ #print "new message id = $message_id\n"; # Was useful for debugging
+}
+
+
+
+$cc = "";
+
+sub send_message
+{
+ my $to = join (", ", unique_email_list(@to));
+
+ %mail = ( To => $to,
+ From => $from,
+ CC => $cc,
+ Subject => $subject,
+ Message => $message,
+ 'Reply-to' => $from,
+ 'In-Reply-To' => $reply_to,
+ 'Message-ID' => $message_id,
+ 'X-Mailer' => "git-send-email",
+ );
+
+ $mail{smtp} = $smtp_server;
+ $mailcfg{mime} = 0;
+
+ #print Data::Dumper->Dump([\%mail],[qw(*mail)]);
+
+ sendmail(%mail) or die $Mail::Sendmail::error;
+
+ print "OK. Log says:\n", $Mail::Sendmail::log;
+ print "\n\n"
+}
+
+
+$reply_to = $initial_reply_to;
+make_message_id();
+$subject = $initial_subject;
+
+foreach my $t (@files) {
+ my $F = $t;
+ open(F,"<",$t) or die "can't open file $t";
+
+ @cc = ();
+ my $found_mbox = 0;
+ my $header_done = 0;
+ $message = "";
+ while(<F>) {
+ if (!$header_done) {
+ $found_mbox = 1, next if (/^From /);
+ chomp;
+
+ if ($found_mbox) {
+ if (/^Subject:\s+(.*)$/) {
+ $subject = $1;
+
+ } elsif (/^(Cc|From):\s+(.*)$/) {
+ printf("(mbox) Adding cc: %s from line '%s'\n",
+ $2, $_);
+ push @cc, $2;
+ }
+
+ } else {
+ # In the traditional
+ # "send lots of email" format,
+ # line 1 = cc
+ # line 2 = subject
+ # So let's support that, too.
+ if (@cc == 0) {
+ printf("(non-mbox) Adding cc: %s from line '%s'\n",
+ $_, $_);
+
+ push @cc, $_;
+
+ } elsif (!defined $subject) {
+ $subject = $_;
+ }
+ }
+
+ # A whitespace line will terminate the headers
+ if (m/^\s*$/) {
+ $header_done = 1;
+ }
+ } else {
+ $message .= $_;
+ if (/^Signed-off-by: (.*)$/i) {
+ my $c = $1;
+ chomp $c;
+ push @cc, $c;
+ printf("(sob) Adding cc: %s from line '%s'\n",
+ $c, $_);
+ }
+ }
+ }
+ close F;
+
+ $cc = join(", ", unique_email_list(@cc));
+
+ send_message();
+
+ # set up for the next message
+ if ($chain_reply_to || length($reply_to) == 0) {
+ $reply_to = $message_id;
+ }
+ make_message_id();
+}
+
+if ($compose) {
+ cleanup_compose_files();
+}
+
+sub cleanup_compose_files() {
+ unlink($compose_filename, $compose_filename . ".final");
+
+}
+
+
+
+sub unique_email_list(@) {
+ my %seen;
+ my @emails;
+
+ foreach my $entry (@_) {
+ my $clean = Email::Valid->address($entry);
+ next if $seen{$clean}++;
+ push @emails, $entry;
+ }
+ return @emails;
+}
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
new file mode 100755
index 000000000..dbb98842b
--- /dev/null
+++ b/git-sh-setup.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Set up GIT_DIR and GIT_OBJECT_DIRECTORY
+# and return true if everything looks ok
+#
+: ${GIT_DIR=.git}
+: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+
+# Having this variable in your environment would break scripts because
+# you would cause "cd" to be be taken to unexpected places. If you
+# like CDPATH, define it for your interactive shell sessions without
+# exporting it.
+unset CDPATH
+
+die() {
+ echo >&2 "$@"
+ exit 1
+}
+
+case "$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD 2>/dev/null)" in
+refs/*) : ;;
+*) false ;;
+esac &&
+[ -d "$GIT_DIR/refs" ] &&
+[ -d "$GIT_OBJECT_DIRECTORY/" ]
diff --git a/git-shortlog.perl b/git-shortlog.perl
new file mode 100755
index 000000000..0b14f833e
--- /dev/null
+++ b/git-shortlog.perl
@@ -0,0 +1,200 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+my (%mailmap);
+my (%email);
+my (%map);
+my $pstate = 1;
+my $n_records = 0;
+my $n_output = 0;
+
+sub shortlog_entry($$) {
+ my ($name, $desc) = @_;
+ my $key = $name;
+
+ $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g;
+ $desc =~ s#\[PATCH\] ##g;
+
+ # store description in array, in email->{desc list} map
+ if (exists $map{$key}) {
+ # grab ref
+ my $obj = $map{$key};
+
+ # add desc to array
+ push(@$obj, $desc);
+ } else {
+ # create new array, containing 1 item
+ my @arr = ($desc);
+
+ # store ref to array
+ $map{$key} = \@arr;
+ }
+}
+
+# sort comparison function
+sub by_name($$) {
+ my ($a, $b) = @_;
+
+ uc($a) cmp uc($b);
+}
+
+sub shortlog_output {
+ my ($obj, $key, $desc);
+
+ foreach $key (sort by_name keys %map) {
+ # output author
+ printf "%s:\n", $key;
+
+ # output author's 1-line summaries
+ $obj = $map{$key};
+ foreach $desc (reverse @$obj) {
+ print " $desc\n";
+ $n_output++;
+ }
+
+ # blank line separating author from next author
+ print "\n";
+ }
+}
+
+sub changelog_input {
+ my ($author, $desc);
+
+ while (<>) {
+ # get author and email
+ if ($pstate == 1) {
+ my ($email);
+
+ next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/;
+
+ $n_records++;
+
+ $author = $1;
+ $email = $2;
+ $desc = undef;
+
+ # cset author fixups
+ if (exists $mailmap{$email}) {
+ $author = $mailmap{$email};
+ } elsif (exists $mailmap{$author}) {
+ $author = $mailmap{$author};
+ } elsif (!$author) {
+ $author = $email;
+ }
+ $email{$author}{$email}++;
+ $pstate++;
+ }
+
+ # skip to blank line
+ elsif ($pstate == 2) {
+ next unless /^\s*$/;
+ $pstate++;
+ }
+
+ # skip to non-blank line
+ elsif ($pstate == 3) {
+ next unless /^\s*?(.*)/;
+
+ # skip lines that are obviously not
+ # a 1-line cset description
+ next if /^\s*From: /;
+
+ chomp;
+ $desc = $1;
+
+ &shortlog_entry($author, $desc);
+
+ $pstate = 1;
+ }
+
+ else {
+ die "invalid parse state $pstate";
+ }
+ }
+}
+
+sub read_mailmap {
+ my ($fh, $mailmap) = @_;
+ while (<$fh>) {
+ chomp;
+ if (/^([^#].*?)\s*<(.*)>/) {
+ $mailmap->{$2} = $1;
+ }
+ }
+}
+
+sub setup_mailmap {
+ read_mailmap(\*DATA, \%mailmap);
+ if (-f '.mailmap') {
+ my $fh = undef;
+ open $fh, '<', '.mailmap';
+ read_mailmap($fh, \%mailmap);
+ close $fh;
+ }
+}
+
+sub finalize {
+ #print "\n$n_records records parsed.\n";
+
+ if ($n_records != $n_output) {
+ die "parse error: input records != output records\n";
+ }
+ if (0) {
+ for my $author (sort keys %email) {
+ my $e = $email{$author};
+ for my $email (sort keys %$e) {
+ print STDERR "$author <$email>\n";
+ }
+ }
+ }
+}
+
+&setup_mailmap;
+&changelog_input;
+&shortlog_output;
+&finalize;
+exit(0);
+
+
+__DATA__
+#
+# Even with git, we don't always have name translations.
+# So have an email->real name table to translate the
+# (hopefully few) missing names
+#
+Adrian Bunk <bunk@stusta.de>
+Andreas Herrmann <aherrman@de.ibm.com>
+Andrew Morton <akpm@osdl.org>
+Andrew Vasquez <andrew.vasquez@qlogic.com>
+Christoph Hellwig <hch@lst.de>
+Corey Minyard <minyard@acm.org>
+David Woodhouse <dwmw2@shinybook.infradead.org>
+Domen Puncer <domen@coderock.org>
+Douglas Gilbert <dougg@torque.net>
+Ed L Cashin <ecashin@coraid.com>
+Evgeniy Polyakov <johnpol@2ka.mipt.ru>
+Felix Moeller <felix@derklecks.de>
+Frank Zago <fzago@systemfabricworks.com>
+Greg Kroah-Hartman <gregkh@suse.de>
+James Bottomley <jejb@mulgrave.(none)>
+James Bottomley <jejb@titanic.il.steeleye.com>
+Jeff Garzik <jgarzik@pretzel.yyz.us>
+Jens Axboe <axboe@suse.de>
+Kay Sievers <kay.sievers@vrfy.org>
+Mitesh shah <mshah@teja.com>
+Morten Welinder <terra@gnome.org>
+Morten Welinder <welinder@anemone.rentec.com>
+Morten Welinder <welinder@darter.rentec.com>
+Morten Welinder <welinder@troll.com>
+Nguyen Anh Quynh <aquynh@gmail.com>
+Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
+Peter A Jonsson <pj@ludd.ltu.se>
+Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
+Rudolf Marek <R.Marek@sh.cvut.cz>
+Rui Saraiva <rmps@joel.ist.utl.pt>
+Sachin P Sant <ssant@in.ibm.com>
+Santtu Hyrkk,Av(B <santtu.hyrkko@gmail.com>
+Simon Kelley <simon@thekelleys.org.uk>
+Tejun Heo <htejun@gmail.com>
+Tony Luck <tony.luck@intel.com>
diff --git a/git-status.sh b/git-status.sh
new file mode 100755
index 000000000..837f334d8
--- /dev/null
+++ b/git-status.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+#
+. git-sh-setup || die "Not a git archive"
+
+report () {
+ header="#
+# $1:
+# ($2)
+#
+"
+ trailer=""
+ while read status name newname
+ do
+ echo -n "$header"
+ header=""
+ trailer="#
+"
+ case "$status" in
+ M ) echo "# modified: $name";;
+ D*) echo "# deleted: $name";;
+ T ) echo "# typechange: $name";;
+ C*) echo "# copied: $name -> $newname";;
+ R*) echo "# renamed: $name -> $newname";;
+ A*) echo "# new file: $name";;
+ U ) echo "# unmerged: $name";;
+ esac
+ done
+ echo -n "$trailer"
+ [ "$header" ]
+}
+
+branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD)
+case "$branch" in
+refs/heads/master) ;;
+*) echo "# On branch $branch" ;;
+esac
+
+git-update-index -q --unmerged --refresh || exit
+
+if GIT_DIR="$GIT_DIR" git-rev-parse --verify HEAD >/dev/null 2>&1
+then
+ git-diff-index -M --cached --name-status --diff-filter=MDTCRA HEAD |
+ sed -e '
+ s/\\/\\\\/g
+ s/ /\\ /g
+ ' |
+ report "Updated but not checked in" "will commit"
+
+ committable="$?"
+else
+ echo '#
+# Initial commit
+#'
+ git-ls-files |
+ sed -e '
+ s/\\/\\\\/g
+ s/ /\\ /g
+ s/^/A /
+ ' |
+ report "Updated but not checked in" "will commit"
+
+ committable="$?"
+fi
+
+git-diff-files --name-status |
+sed -e '
+ s/\\/\\\\/g
+ s/ /\\ /g
+' |
+report "Changed but not updated" "use git-update-index to mark for commit"
+
+
+if test -f "$GIT_DIR/info/exclude"
+then
+ git-ls-files -z --others \
+ --exclude-from="$GIT_DIR/info/exclude" \
+ --exclude-per-directory=.gitignore
+else
+ git-ls-files -z --others \
+ --exclude-per-directory=.gitignore
+fi |
+perl -e '$/ = "\0";
+ my $shown = 0;
+ while (<>) {
+ chomp;
+ s|\\|\\\\|g;
+ s|\t|\\t|g;
+ s|\n|\\n|g;
+ s/^/# /;
+ if (!$shown) {
+ print "#\n# Untracked files:\n";
+ print "# (use \"git add\" to add to commit)\n#\n";
+ $shown = 1;
+ }
+ print "$_\n";
+ }
+'
+
+case "$committable" in
+0)
+ echo "nothing to commit"
+ exit 1
+esac
+exit 0
diff --git a/git-svnimport.perl b/git-svnimport.perl
new file mode 100755
index 000000000..45d77c5ba
--- /dev/null
+++ b/git-svnimport.perl
@@ -0,0 +1,780 @@
+#!/usr/bin/perl -w
+
+# This tool is copyright (c) 2005, Matthias Urlichs.
+# It is released under the Gnu Public License, version 2.
+#
+# The basic idea is to pull and analyze SVN changes.
+#
+# Checking out the files is done by a single long-running SVN connection.
+#
+# The head revision is on branch "origin" by default.
+# You can change that with the '-o' option.
+
+require 5.008; # for shell-safe open("-|",LIST)
+use strict;
+use warnings;
+use Getopt::Std;
+use File::Spec;
+use File::Temp qw(tempfile);
+use File::Path qw(mkpath);
+use File::Basename qw(basename dirname);
+use Time::Local;
+use IO::Pipe;
+use POSIX qw(strftime dup2);
+use IPC::Open2;
+use SVN::Core;
+use SVN::Ra;
+
+die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1";
+
+$SIG{'PIPE'}="IGNORE";
+$ENV{'TZ'}="UTC";
+
+our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_s,$opt_l,$opt_d,$opt_D);
+
+sub usage() {
+ print STDERR <<END;
+Usage: ${\basename $0} # fetch/update GIT from SVN
+ [-o branch-for-HEAD] [-h] [-v] [-l max_num_changes]
+ [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
+ [-d|-D] [-i] [-u] [-s start_chg] [-m] [-M regex] [SVN_URL]
+END
+ exit(1);
+}
+
+getopts("b:C:dDhil:mM:o:s:t:T:uv") or usage();
+usage if $opt_h;
+
+my $tag_name = $opt_t || "tags";
+my $trunk_name = $opt_T || "trunk";
+my $branch_name = $opt_b || "branches";
+
+@ARGV == 1 or @ARGV == 2 or usage();
+
+$opt_o ||= "origin";
+$opt_s ||= 1;
+my $git_tree = $opt_C;
+$git_tree ||= ".";
+
+my $svn_url = $ARGV[0];
+my $svn_dir = $ARGV[1];
+
+our @mergerx = ();
+if ($opt_m) {
+ @mergerx = ( qr/\W(?:from|of|merge|merging|merged) (\w+)/i );
+}
+if ($opt_M) {
+ push (@mergerx, qr/$opt_M/);
+}
+
+select(STDERR); $|=1; select(STDOUT);
+
+
+package SVNconn;
+# Basic SVN connection.
+# We're only interested in connecting and downloading, so ...
+
+use File::Spec;
+use File::Temp qw(tempfile);
+use POSIX qw(strftime dup2);
+
+sub new {
+ my($what,$repo) = @_;
+ $what=ref($what) if ref($what);
+
+ my $self = {};
+ $self->{'buffer'} = "";
+ bless($self,$what);
+
+ $repo =~ s#/+$##;
+ $self->{'fullrep'} = $repo;
+ $self->conn();
+
+ return $self;
+}
+
+sub conn {
+ my $self = shift;
+ my $repo = $self->{'fullrep'};
+ my $s = SVN::Ra->new($repo);
+
+ die "SVN connection to $repo: $!\n" unless defined $s;
+ $self->{'svn'} = $s;
+ $self->{'repo'} = $repo;
+ $self->{'maxrev'} = $s->get_latest_revnum();
+}
+
+sub file {
+ my($self,$path,$rev) = @_;
+
+ my ($fh, $name) = tempfile('gitsvn.XXXXXX',
+ DIR => File::Spec->tmpdir(), UNLINK => 1);
+
+ print "... $rev $path ...\n" if $opt_v;
+ my $pool = SVN::Pool->new();
+ eval { $self->{'svn'}->get_file($path,$rev,$fh,$pool); };
+ $pool->clear;
+ if($@) {
+ return undef if $@ =~ /Attempted to get checksum/;
+ die $@;
+ }
+ close ($fh);
+
+ return $name;
+}
+
+package main;
+use URI;
+
+my $svn = $svn_url;
+$svn .= "/$svn_dir" if defined $svn_dir;
+$svn = SVNconn->new($svn);
+
+my $lwp_ua;
+if($opt_d or $opt_D) {
+ $svn_url = URI->new($svn_url)->canonical;
+ if($opt_D) {
+ $svn_dir =~ s#/*$#/#;
+ } else {
+ $svn_dir = "";
+ }
+ if ($svn_url->scheme eq "http") {
+ use LWP::UserAgent;
+ $lwp_ua = LWP::UserAgent->new(keep_alive => 1, requests_redirectable => []);
+ } else {
+ print STDERR "Warning: not HTTP; turning off direct file access\n";
+ $opt_d=0;
+ }
+}
+
+sub pdate($) {
+ my($d) = @_;
+ $d =~ m#(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)#
+ or die "Unparseable date: $d\n";
+ my $y=$1; $y-=1900 if $y>1900;
+ return timegm($6||0,$5,$4,$3,$2-1,$y);
+}
+
+sub getwd() {
+ my $pwd = `pwd`;
+ chomp $pwd;
+ return $pwd;
+}
+
+
+sub get_headref($$) {
+ my $name = shift;
+ my $git_dir = shift;
+ my $sha;
+
+ if (open(C,"$git_dir/refs/heads/$name")) {
+ chomp($sha = <C>);
+ close(C);
+ length($sha) == 40
+ or die "Cannot get head id for $name ($sha): $!\n";
+ }
+ return $sha;
+}
+
+
+-d $git_tree
+ or mkdir($git_tree,0777)
+ or die "Could not create $git_tree: $!";
+chdir($git_tree);
+
+my $orig_branch = "";
+my $forward_master = 0;
+my %branches;
+
+my $git_dir = $ENV{"GIT_DIR"} || ".git";
+$git_dir = getwd()."/".$git_dir unless $git_dir =~ m#^/#;
+$ENV{"GIT_DIR"} = $git_dir;
+my $orig_git_index;
+$orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
+my ($git_ih, $git_index) = tempfile('gitXXXXXX', SUFFIX => '.idx',
+ DIR => File::Spec->tmpdir());
+close ($git_ih);
+$ENV{GIT_INDEX_FILE} = $git_index;
+my $maxnum = 0;
+my $last_rev = "";
+my $last_branch;
+my $current_rev = $opt_s-1;
+unless(-d $git_dir) {
+ system("git-init-db");
+ die "Cannot init the GIT db at $git_tree: $?\n" if $?;
+ system("git-read-tree");
+ die "Cannot init an empty tree: $?\n" if $?;
+
+ $last_branch = $opt_o;
+ $orig_branch = "";
+} else {
+ -f "$git_dir/refs/heads/$opt_o"
+ or die "Branch '$opt_o' does not exist.\n".
+ "Either use the correct '-o branch' option,\n".
+ "or import to a new repository.\n";
+
+ -f "$git_dir/svn2git"
+ or die "'$git_dir/svn2git' does not exist.\n".
+ "You need that file for incremental imports.\n";
+ open(F, "git-symbolic-ref HEAD |") or
+ die "Cannot run git-symbolic-ref: $!\n";
+ chomp ($last_branch = <F>);
+ $last_branch = basename($last_branch);
+ close(F);
+ unless($last_branch) {
+ warn "Cannot read the last branch name: $! -- assuming 'master'\n";
+ $last_branch = "master";
+ }
+ $orig_branch = $last_branch;
+ $last_rev = get_headref($orig_branch, $git_dir);
+ if (-f "$git_dir/SVN2GIT_HEAD") {
+ die <<EOM;
+SVN2GIT_HEAD exists.
+Make sure your working directory corresponds to HEAD and remove SVN2GIT_HEAD.
+You may need to run
+
+ git-read-tree -m -u SVN2GIT_HEAD HEAD
+EOM
+ }
+ system('cp', "$git_dir/HEAD", "$git_dir/SVN2GIT_HEAD");
+
+ $forward_master =
+ $opt_o ne 'master' && -f "$git_dir/refs/heads/master" &&
+ system('cmp', '-s', "$git_dir/refs/heads/master",
+ "$git_dir/refs/heads/$opt_o") == 0;
+
+ # populate index
+ system('git-read-tree', $last_rev);
+ die "read-tree failed: $?\n" if $?;
+
+ # Get the last import timestamps
+ open my $B,"<", "$git_dir/svn2git";
+ while(<$B>) {
+ chomp;
+ my($num,$branch,$ref) = split;
+ $branches{$branch}{$num} = $ref;
+ $branches{$branch}{"LAST"} = $ref;
+ $current_rev = $num if $current_rev < $num;
+ }
+ close($B);
+}
+-d $git_dir
+ or die "Could not create git subdir ($git_dir).\n";
+
+open BRANCHES,">>", "$git_dir/svn2git";
+
+sub node_kind($$$) {
+ my ($branch, $path, $revision) = @_;
+ my $pool=SVN::Pool->new;
+ my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool);
+ $pool->clear;
+ return $kind;
+}
+
+sub revert_split_path($$) {
+ my($branch,$path) = @_;
+
+ my $svnpath;
+ $path = "" if $path eq "/"; # this should not happen, but ...
+ if($branch eq "/") {
+ $svnpath = "$trunk_name/$path";
+ } elsif($branch =~ m#^/#) {
+ $svnpath = "$tag_name$branch/$path";
+ } else {
+ $svnpath = "$branch_name/$branch/$path";
+ }
+
+ $svnpath =~ s#/+$##;
+ return $svnpath;
+}
+
+sub get_file($$$) {
+ my($rev,$branch,$path) = @_;
+
+ my $svnpath = revert_split_path($branch,$path);
+
+ # now get it
+ my $name;
+ if($opt_d) {
+ my($req,$res);
+
+ # /svn/!svn/bc/2/django/trunk/django-docs/build.py
+ my $url=$svn_url->clone();
+ $url->path($url->path."/!svn/bc/$rev/$svn_dir$svnpath");
+ print "... $path...\n" if $opt_v;
+ $req = HTTP::Request->new(GET => $url);
+ $res = $lwp_ua->request($req);
+ if ($res->is_success) {
+ my $fh;
+ ($fh, $name) = tempfile('gitsvn.XXXXXX',
+ DIR => File::Spec->tmpdir(), UNLINK => 1);
+ print $fh $res->content;
+ close($fh) or die "Could not write $name: $!\n";
+ } else {
+ return undef if $res->code == 301; # directory?
+ die $res->status_line." at $url\n";
+ }
+ } else {
+ $name = $svn->file("/$svnpath",$rev);
+ return undef unless defined $name;
+ }
+
+ open my $F, '-|', "git-hash-object", "-w", $name
+ or die "Cannot create object: $!\n";
+ my $sha = <$F>;
+ chomp $sha;
+ close $F;
+ unlink $name;
+ my $mode = "0644"; # SV does not seem to store any file modes
+ return [$mode, $sha, $path];
+}
+
+sub split_path($$) {
+ my($rev,$path) = @_;
+ my $branch;
+
+ if($path =~ s#^/\Q$tag_name\E/([^/]+)/?##) {
+ $branch = "/$1";
+ } elsif($path =~ s#^/\Q$trunk_name\E/?##) {
+ $branch = "/";
+ } elsif($path =~ s#^/\Q$branch_name\E/([^/]+)/?##) {
+ $branch = $1;
+ } else {
+ my %no_error = (
+ "/" => 1,
+ "/$tag_name" => 1,
+ "/$branch_name" => 1
+ );
+ print STDERR "$rev: Unrecognized path: $path\n" unless (defined $no_error{$path});
+ return ()
+ }
+ $path = "/" if $path eq "";
+ return ($branch,$path);
+}
+
+sub branch_rev($$) {
+
+ my ($srcbranch,$uptorev) = @_;
+
+ my $bbranches = $branches{$srcbranch};
+ my @revs = reverse sort { ($a eq 'LAST' ? 0 : $a) <=> ($b eq 'LAST' ? 0 : $b) } keys %$bbranches;
+ my $therev;
+ foreach my $arev(@revs) {
+ next if ($arev eq 'LAST');
+ if ($arev <= $uptorev) {
+ $therev = $arev;
+ last;
+ }
+ }
+ return $therev;
+}
+
+sub copy_path($$$$$$$$) {
+ # Somebody copied a whole subdirectory.
+ # We need to find the index entries from the old version which the
+ # SVN log entry points to, and add them to the new place.
+
+ my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
+
+ my($srcbranch,$srcpath) = split_path($rev,$oldpath);
+ unless(defined $srcbranch) {
+ print "Path not found when copying from $oldpath @ $rev\n";
+ return;
+ }
+ my $therev = branch_rev($srcbranch, $rev);
+ my $gitrev = $branches{$srcbranch}{$therev};
+ unless($gitrev) {
+ print STDERR "$newrev:$newbranch: could not find $oldpath \@ $rev\n";
+ return;
+ }
+ if ($srcbranch ne $newbranch) {
+ push(@$parents, $branches{$srcbranch}{'LAST'});
+ }
+ print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
+ if ($node_kind eq $SVN::Node::dir) {
+ $srcpath =~ s#/*$#/#;
+ }
+
+ open my $f,"-|","git-ls-tree","-r","-z",$gitrev,$srcpath;
+ local $/ = "\0";
+ while(<$f>) {
+ chomp;
+ my($m,$p) = split(/\t/,$_,2);
+ my($mode,$type,$sha1) = split(/ /,$m);
+ next if $type ne "blob";
+ if ($node_kind eq $SVN::Node::dir) {
+ $p = $path . substr($p,length($srcpath)-1);
+ } else {
+ $p = $path;
+ }
+ push(@$new,[$mode,$sha1,$p]);
+ }
+ close($f) or
+ print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
+}
+
+sub commit {
+ my($branch, $changed_paths, $revision, $author, $date, $message) = @_;
+ my($author_name,$author_email,$dest);
+ my(@old,@new,@parents);
+
+ if (not defined $author) {
+ $author_name = $author_email = "unknown";
+ } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
+ ($author_name, $author_email) = ($1, $2);
+ } else {
+ $author =~ s/^<(.*)>$/$1/;
+ $author_name = $author_email = $author;
+ }
+ $date = pdate($date);
+
+ my $tag;
+ my $parent;
+ if($branch eq "/") { # trunk
+ $parent = $opt_o;
+ } elsif($branch =~ m#^/(.+)#) { # tag
+ $tag = 1;
+ $parent = $1;
+ } else { # "normal" branch
+ # nothing to do
+ $parent = $branch;
+ }
+ $dest = $parent;
+
+ my $prev = $changed_paths->{"/"};
+ if($prev and $prev->[0] eq "A") {
+ delete $changed_paths->{"/"};
+ my $oldpath = $prev->[1];
+ my $rev;
+ if(defined $oldpath) {
+ my $p;
+ ($parent,$p) = split_path($revision,$oldpath);
+ if($parent eq "/") {
+ $parent = $opt_o;
+ } else {
+ $parent =~ s#^/##; # if it's a tag
+ }
+ } else {
+ $parent = undef;
+ }
+ }
+
+ my $rev;
+ if($revision > $opt_s and defined $parent) {
+ open(H,"git-rev-parse --verify $parent |");
+ $rev = <H>;
+ close(H) or do {
+ print STDERR "$revision: cannot find commit '$parent'!\n";
+ return;
+ };
+ chop $rev;
+ if(length($rev) != 40) {
+ print STDERR "$revision: cannot find commit '$parent'!\n";
+ return;
+ }
+ $rev = $branches{($parent eq $opt_o) ? "/" : $parent}{"LAST"};
+ if($revision != $opt_s and not $rev) {
+ print STDERR "$revision: do not know ancestor for '$parent'!\n";
+ return;
+ }
+ } else {
+ $rev = undef;
+ }
+
+# if($prev and $prev->[0] eq "A") {
+# if(not $tag) {
+# unless(open(H,"> $git_dir/refs/heads/$branch")) {
+# print STDERR "$revision: Could not create branch $branch: $!\n";
+# $state=11;
+# next;
+# }
+# print H "$rev\n"
+# or die "Could not write branch $branch: $!";
+# close(H)
+# or die "Could not write branch $branch: $!";
+# }
+# }
+ if(not defined $rev) {
+ unlink($git_index);
+ } elsif ($rev ne $last_rev) {
+ print "Switching from $last_rev to $rev ($branch)\n" if $opt_v;
+ system("git-read-tree", $rev);
+ die "read-tree failed for $rev: $?\n" if $?;
+ $last_rev = $rev;
+ }
+
+ push (@parents, $rev) if defined $rev;
+
+ my $cid;
+ if($tag and not %$changed_paths) {
+ $cid = $rev;
+ } else {
+ my @paths = sort keys %$changed_paths;
+ foreach my $path(@paths) {
+ my $action = $changed_paths->{$path};
+
+ if ($action->[0] eq "R") {
+ # refer to a file/tree in an earlier commit
+ push(@old,$path); # remove any old stuff
+ }
+ if(($action->[0] eq "A") || ($action->[0] eq "R")) {
+ my $node_kind = node_kind($branch,$path,$revision);
+ if($action->[1]) {
+ copy_path($revision,$branch,$path,$action->[1],$action->[2],$node_kind,\@new,\@parents);
+ } elsif ($node_kind eq $SVN::Node::file) {
+ my $f = get_file($revision,$branch,$path);
+ if ($f) {
+ push(@new,$f) if $f;
+ } else {
+ my $opath = $action->[3];
+ print STDERR "$revision: $branch: could not fetch '$opath'\n";
+ }
+ }
+ } elsif ($action->[0] eq "D") {
+ push(@old,$path);
+ } elsif ($action->[0] eq "M") {
+ my $node_kind = node_kind($branch,$path,$revision);
+ if ($node_kind eq $SVN::Node::file) {
+ my $f = get_file($revision,$branch,$path);
+ push(@new,$f) if $f;
+ }
+ } else {
+ die "$revision: unknown action '".$action->[0]."' for $path\n";
+ }
+ }
+
+ if(@old) {
+ open my $F, "-|", "git-ls-files", "-z", @old or die $!;
+ @old = ();
+ local $/ = "\0";
+ while(<$F>) {
+ chomp;
+ push(@old,$_);
+ }
+ close($F);
+
+ while(@old) {
+ my @o2;
+ if(@old > 55) {
+ @o2 = splice(@old,0,50);
+ } else {
+ @o2 = @old;
+ @old = ();
+ }
+ system("git-update-index","--force-remove","--",@o2);
+ die "Cannot remove files: $?\n" if $?;
+ }
+ }
+ while(@new) {
+ my @n2;
+ if(@new > 12) {
+ @n2 = splice(@new,0,10);
+ } else {
+ @n2 = @new;
+ @new = ();
+ }
+ system("git-update-index","--add",
+ (map { ('--cacheinfo', @$_) } @n2));
+ die "Cannot add files: $?\n" if $?;
+ }
+
+ my $pid = open(C,"-|");
+ die "Cannot fork: $!" unless defined $pid;
+ unless($pid) {
+ exec("git-write-tree");
+ die "Cannot exec git-write-tree: $!\n";
+ }
+ chomp(my $tree = <C>);
+ length($tree) == 40
+ or die "Cannot get tree id ($tree): $!\n";
+ close(C)
+ or die "Error running git-write-tree: $?\n";
+ print "Tree ID $tree\n" if $opt_v;
+
+ my $pr = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+ my $pw = IO::Pipe->new() or die "Cannot open pipe: $!\n";
+ $pid = fork();
+ die "Fork: $!\n" unless defined $pid;
+ unless($pid) {
+ $pr->writer();
+ $pw->reader();
+ open(OUT,">&STDOUT");
+ dup2($pw->fileno(),0);
+ dup2($pr->fileno(),1);
+ $pr->close();
+ $pw->close();
+
+ my @par = ();
+
+ # loose detection of merges
+ # based on the commit msg
+ foreach my $rx (@mergerx) {
+ if ($message =~ $rx) {
+ my $mparent = $1;
+ if ($mparent eq 'HEAD') { $mparent = $opt_o };
+ if ( -e "$git_dir/refs/heads/$mparent") {
+ $mparent = get_headref($mparent, $git_dir);
+ push (@parents, $mparent);
+ print OUT "Merge parent branch: $mparent\n" if $opt_v;
+ }
+ }
+ }
+ my %seen_parents = ();
+ my @unique_parents = grep { ! $seen_parents{$_} ++ } @parents;
+ foreach my $bparent (@unique_parents) {
+ push @par, '-p', $bparent;
+ print OUT "Merge parent branch: $bparent\n" if $opt_v;
+ }
+
+ exec("env",
+ "GIT_AUTHOR_NAME=$author_name",
+ "GIT_AUTHOR_EMAIL=$author_email",
+ "GIT_AUTHOR_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+ "GIT_COMMITTER_NAME=$author_name",
+ "GIT_COMMITTER_EMAIL=$author_email",
+ "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)),
+ "git-commit-tree", $tree,@par);
+ die "Cannot exec git-commit-tree: $!\n";
+ }
+ $pw->writer();
+ $pr->reader();
+
+ $message =~ s/[\s\n]+\z//;
+
+ print $pw "$message\n"
+ or die "Error writing to git-commit-tree: $!\n";
+ $pw->close();
+
+ print "Committed change $revision:$branch ".strftime("%Y-%m-%d %H:%M:%S",gmtime($date)).")\n" if $opt_v;
+ chomp($cid = <$pr>);
+ length($cid) == 40
+ or die "Cannot get commit id ($cid): $!\n";
+ print "Commit ID $cid\n" if $opt_v;
+ $pr->close();
+
+ waitpid($pid,0);
+ die "Error running git-commit-tree: $?\n" if $?;
+ }
+
+ if (not defined $cid) {
+ $cid = $branches{"/"}{"LAST"};
+ }
+
+ if(not defined $dest) {
+ print "... no known parent\n" if $opt_v;
+ } elsif(not $tag) {
+ print "Writing to refs/heads/$dest\n" if $opt_v;
+ open(C,">$git_dir/refs/heads/$dest") and
+ print C ("$cid\n") and
+ close(C)
+ or die "Cannot write branch $dest for update: $!\n";
+ }
+
+ if($tag) {
+ my($in, $out) = ('','');
+ $last_rev = "-" if %$changed_paths;
+ # the tag was 'complex', i.e. did not refer to a "real" revision
+
+ $dest =~ tr/_/\./ if $opt_u;
+ $branch = $dest;
+
+ my $pid = open2($in, $out, 'git-mktag');
+ print $out ("object $cid\n".
+ "type commit\n".
+ "tag $dest\n".
+ "tagger $author_name <$author_email>\n") and
+ close($out)
+ or die "Cannot create tag object $dest: $!\n";
+
+ my $tagobj = <$in>;
+ chomp $tagobj;
+
+ if ( !close($in) or waitpid($pid, 0) != $pid or
+ $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
+ die "Cannot create tag object $dest: $!\n";
+ }
+
+ open(C,">$git_dir/refs/tags/$dest") and
+ print C ("$tagobj\n") and
+ close(C)
+ or die "Cannot create tag $branch: $!\n";
+
+ print "Created tag '$dest' on '$branch'\n" if $opt_v;
+ }
+ $branches{$branch}{"LAST"} = $cid;
+ $branches{$branch}{$revision} = $cid;
+ $last_rev = $cid;
+ print BRANCHES "$revision $branch $cid\n";
+ print "DONE: $revision $dest $cid\n" if $opt_v;
+}
+
+my ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
+sub _commit_all {
+ ($changed_paths, $revision, $author, $date, $message, $pool) = @_;
+ my %p;
+ while(my($path,$action) = each %$changed_paths) {
+ $p{$path} = [ $action->action,$action->copyfrom_path, $action->copyfrom_rev, $path ];
+ }
+ $changed_paths = \%p;
+}
+
+sub commit_all {
+ my %done;
+ my @col;
+ my $pref;
+ my $branch;
+
+ while(my($path,$action) = each %$changed_paths) {
+ ($branch,$path) = split_path($revision,$path);
+ next if not defined $branch;
+ $done{$branch}{$path} = $action;
+ }
+ while(($branch,$changed_paths) = each %done) {
+ commit($branch, $changed_paths, $revision, $author, $date, $message);
+ }
+}
+
+while(++$current_rev <= $svn->{'maxrev'}) {
+ if (defined $opt_l) {
+ $opt_l--;
+ if ($opt_l < 0) {
+ last;
+ }
+ }
+ my $pool=SVN::Pool->new;
+ $svn->{'svn'}->get_log("/",$current_rev,$current_rev,1,1,1,\&_commit_all,$pool);
+ $pool->clear;
+ commit_all();
+}
+
+
+unlink($git_index);
+
+if (defined $orig_git_index) {
+ $ENV{GIT_INDEX_FILE} = $orig_git_index;
+} else {
+ delete $ENV{GIT_INDEX_FILE};
+}
+
+# Now switch back to the branch we were in before all of this happened
+if($orig_branch) {
+ print "DONE\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+ system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+ if $forward_master;
+ unless ($opt_i) {
+ system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD');
+ die "read-tree failed: $?\n" if $?;
+ }
+} else {
+ $orig_branch = "master";
+ print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0);
+ system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master")
+ unless -f "$git_dir/refs/heads/master";
+ system('git-update-ref', 'HEAD', "$orig_branch");
+ unless ($opt_i) {
+ system('git checkout');
+ die "checkout failed: $?\n" if $?;
+ }
+}
+unlink("$git_dir/SVN2GIT_HEAD");
+close(BRANCHES);
diff --git a/git-tag.sh b/git-tag.sh
new file mode 100755
index 000000000..137594530
--- /dev/null
+++ b/git-tag.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+# Copyright (c) 2005 Linus Torvalds
+
+. git-sh-setup || die "Not a git archive"
+
+usage () {
+ echo >&2 "Usage: git-tag [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [<head>]"
+ exit 1
+}
+
+annotate=
+signed=
+force=
+message=
+username=
+while case "$#" in 0) break ;; esac
+do
+ case "$1" in
+ -a)
+ annotate=1
+ ;;
+ -s)
+ annotate=1
+ signed=1
+ ;;
+ -f)
+ force=1
+ ;;
+ -m)
+ annotate=1
+ shift
+ message="$1"
+ ;;
+ -u)
+ annotate=1
+ signed=1
+ shift
+ username="$1"
+ ;;
+ -d)
+ shift
+ tag_name="$1"
+ rm "$GIT_DIR/refs/tags/$tag_name" && \
+ echo "Deleted tag $tag_name."
+ exit $?
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+name="$1"
+[ "$name" ] || usage
+if [ -e "$GIT_DIR/refs/tags/$name" -a -z "$force" ]; then
+ die "tag '$name' already exists"
+fi
+shift
+git-check-ref-format "tags/$name" ||
+ die "we do not like '$name' as a tag name."
+
+object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
+type=$(git-cat-file -t $object) || exit 1
+tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
+: ${username:=$(expr "$tagger" : '\(.*>\)')}
+
+trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
+
+if [ "$annotate" ]; then
+ if [ -z "$message" ]; then
+ ( echo "#"
+ echo "# Write a tag message"
+ echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
+ ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR"/TAG_EDITMSG || exit
+ else
+ echo "$message" >"$GIT_DIR"/TAG_EDITMSG
+ fi
+
+ grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
+ git-stripspace >"$GIT_DIR"/TAG_FINALMSG
+
+ [ -s "$GIT_DIR"/TAG_FINALMSG ] || {
+ echo >&2 "No tag message?"
+ exit 1
+ }
+
+ ( echo -e "object $object\ntype $type\ntag $name\ntagger $tagger\n";
+ cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP
+ rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG
+ if [ "$signed" ]; then
+ gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP &&
+ cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP ||
+ die "failed to sign the tag with GPG."
+ fi
+ object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
+fi
+
+leading=`expr "refs/tags/$name" : '\(.*\)/'` &&
+mkdir -p "$GIT_DIR/$leading" &&
+echo $object > "$GIT_DIR/refs/tags/$name"
diff --git a/git-verify-tag.sh b/git-verify-tag.sh
new file mode 100755
index 000000000..ed4c89396
--- /dev/null
+++ b/git-verify-tag.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+. git-sh-setup || die "Not a git archive"
+
+type="$(git-cat-file -t "$1" 2>/dev/null)" ||
+ die "$1: no such object."
+
+test "$type" = tag ||
+ die "$1: cannot verify a non-tag object of type $type."
+
+git-cat-file tag "$1" > .tmp-vtag || exit 1
+cat .tmp-vtag | sed '/-----BEGIN PGP/Q' | gpg --verify .tmp-vtag - || exit 1
+rm -f .tmp-vtag
diff --git a/git-whatchanged.sh b/git-whatchanged.sh
new file mode 100755
index 000000000..85a49fcd8
--- /dev/null
+++ b/git-whatchanged.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+rev_list_args=$(git-rev-parse --sq --default HEAD --revs-only "$@") &&
+diff_tree_args=$(git-rev-parse --sq --no-revs "$@") &&
+
+eval "git-rev-list $rev_list_args" |
+eval "git-diff-tree --stdin --pretty -r $diff_tree_args" |
+LESS="$LESS -S" ${PAGER:-less}
diff --git a/git.c b/git.c
new file mode 100644
index 000000000..bdd3f8d01
--- /dev/null
+++ b/git.c
@@ -0,0 +1,298 @@
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+
+static const char git_usage[] =
+ "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
+
+/* most gui terms set COLUMNS (although some don't export it) */
+static int term_columns(void)
+{
+ char *col_string = getenv("COLUMNS");
+ int n_cols = 0;
+
+ if (col_string && (n_cols = atoi(col_string)) > 0)
+ return n_cols;
+
+ return 80;
+}
+
+static void oom(void)
+{
+ fprintf(stderr, "git: out of memory\n");
+ exit(1);
+}
+
+static inline void mput_char(char c, unsigned int num)
+{
+ while(num--)
+ putchar(c);
+}
+
+static struct cmdname {
+ size_t len;
+ char name[1];
+} **cmdname;
+static int cmdname_alloc, cmdname_cnt;
+
+static void add_cmdname(const char *name, int len)
+{
+ struct cmdname *ent;
+ if (cmdname_alloc <= cmdname_cnt) {
+ cmdname_alloc = cmdname_alloc + 200;
+ cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
+ if (!cmdname)
+ oom();
+ }
+ ent = malloc(sizeof(*ent) + len);
+ if (!ent)
+ oom();
+ ent->len = len;
+ memcpy(ent->name, name, len);
+ ent->name[len] = 0;
+ cmdname[cmdname_cnt++] = ent;
+}
+
+static int cmdname_compare(const void *a_, const void *b_)
+{
+ struct cmdname *a = *(struct cmdname **)a_;
+ struct cmdname *b = *(struct cmdname **)b_;
+ return strcmp(a->name, b->name);
+}
+
+static void pretty_print_string_list(struct cmdname **cmdname, int longest)
+{
+ int cols = 1;
+ int space = longest + 1; /* min 1 SP between words */
+ int max_cols = term_columns() - 1; /* don't print *on* the edge */
+ int i;
+
+ if (space < max_cols)
+ cols = max_cols / space;
+
+ qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
+
+ for (i = 0; i < cmdname_cnt; ) {
+ int c;
+ printf(" ");
+
+ for (c = cols; c && i < cmdname_cnt; i++) {
+ printf("%s", cmdname[i]->name);
+
+ if (--c)
+ mput_char(' ', space - cmdname[i]->len);
+ }
+ putchar('\n');
+ }
+}
+
+static void list_commands(const char *exec_path, const char *pattern)
+{
+ unsigned int longest = 0;
+ char path[PATH_MAX];
+ int dirlen;
+ DIR *dir = opendir(exec_path);
+ struct dirent *de;
+
+ if (!dir) {
+ fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
+ exit(1);
+ }
+
+ dirlen = strlen(exec_path);
+ if (PATH_MAX - 20 < dirlen) {
+ fprintf(stderr, "git: insanely long exec-path '%s'\n",
+ exec_path);
+ exit(1);
+ }
+
+ memcpy(path, exec_path, dirlen);
+ path[dirlen++] = '/';
+
+ while ((de = readdir(dir)) != NULL) {
+ struct stat st;
+ int entlen;
+
+ if (strncmp(de->d_name, "git-", 4))
+ continue;
+ strcpy(path+dirlen, de->d_name);
+ if (stat(path, &st) || /* stat, not lstat */
+ !S_ISREG(st.st_mode) ||
+ !(st.st_mode & S_IXUSR))
+ continue;
+
+ entlen = strlen(de->d_name);
+ if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
+ entlen -= 4;
+
+ if (longest < entlen)
+ longest = entlen;
+
+ add_cmdname(de->d_name + 4, entlen-4);
+ }
+ closedir(dir);
+
+ printf("git commands available in '%s'\n", exec_path);
+ printf("----------------------------");
+ mput_char('-', strlen(exec_path));
+ putchar('\n');
+ pretty_print_string_list(cmdname, longest - 4);
+ putchar('\n');
+}
+
+#ifdef __GNUC__
+static void usage(const char *exec_path, const char *fmt, ...)
+ __attribute__((__format__(__printf__, 2, 3), __noreturn__));
+#endif
+static void usage(const char *exec_path, const char *fmt, ...)
+{
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ printf("git: ");
+ vprintf(fmt, ap);
+ va_end(ap);
+ putchar('\n');
+ }
+ else
+ puts(git_usage);
+
+ putchar('\n');
+
+ if(exec_path)
+ list_commands(exec_path, "git-*");
+
+ exit(1);
+}
+
+static void prepend_to_path(const char *dir, int len)
+{
+ char *path, *old_path = getenv("PATH");
+ int path_len = len;
+
+ if (!old_path)
+ old_path = "/usr/local/bin:/usr/bin:/bin";
+
+ path_len = len + strlen(old_path) + 1;
+
+ path = malloc(path_len + 1);
+ path[path_len + 1] = '\0';
+
+ memcpy(path, dir, len);
+ path[len] = ':';
+ memcpy(path + len + 1, old_path, path_len - len);
+
+ setenv("PATH", path, 1);
+}
+
+static void show_man_page(char *git_cmd)
+{
+ char *page;
+
+ if (!strncmp(git_cmd, "git", 3))
+ page = git_cmd;
+ else {
+ int page_len = strlen(git_cmd) + 4;
+
+ page = malloc(page_len + 1);
+ strcpy(page, "git-");
+ strcpy(page + 4, git_cmd);
+ page[page_len] = 0;
+ }
+
+ execlp("man", "man", page, NULL);
+}
+
+int main(int argc, char **argv, char **envp)
+{
+ char git_command[PATH_MAX + 1];
+ char wd[PATH_MAX + 1];
+ int i, len, show_help = 0;
+ char *exec_path = getenv("GIT_EXEC_PATH");
+
+ getcwd(wd, PATH_MAX);
+
+ if (!exec_path)
+ exec_path = GIT_EXEC_PATH;
+
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+
+ if (strncmp(arg, "--", 2))
+ break;
+
+ arg += 2;
+
+ if (!strncmp(arg, "exec-path", 9)) {
+ arg += 9;
+ if (*arg == '=')
+ exec_path = arg + 1;
+ else {
+ puts(exec_path);
+ exit(0);
+ }
+ }
+ else if (!strcmp(arg, "version")) {
+ printf("git version %s\n", GIT_VERSION);
+ exit(0);
+ }
+ else if (!strcmp(arg, "help"))
+ show_help = 1;
+ else if (!show_help)
+ usage(NULL, NULL);
+ }
+
+ if (i >= argc || show_help) {
+ if (i >= argc)
+ usage(exec_path, NULL);
+
+ show_man_page(argv[i]);
+ }
+
+ if (*exec_path != '/') {
+ if (!getcwd(git_command, sizeof(git_command))) {
+ fprintf(stderr,
+ "git: cannot determine current directory");
+ exit(1);
+ }
+ len = strlen(git_command);
+
+ /* Trivial cleanup */
+ while (!strncmp(exec_path, "./", 2)) {
+ exec_path += 2;
+ while (*exec_path == '/')
+ *exec_path++;
+ }
+ snprintf(git_command + len, sizeof(git_command) - len,
+ "/%s", exec_path);
+ }
+ else
+ strcpy(git_command, exec_path);
+ len = strlen(git_command);
+ prepend_to_path(git_command, len);
+
+ strncat(&git_command[len], "/git-", sizeof(git_command) - len);
+ len += 5;
+ strncat(&git_command[len], argv[i], sizeof(git_command) - len);
+
+ if (access(git_command, X_OK))
+ usage(exec_path, "'%s' is not a git-command", argv[i]);
+
+ /* execve() can only ever return if it fails */
+ execve(git_command, &argv[i], envp);
+ printf("Failed to run command '%s': %s\n", git_command, strerror(errno));
+
+ return 1;
+}
diff --git a/git.spec.in b/git.spec.in
new file mode 100644
index 000000000..96dfc1de5
--- /dev/null
+++ b/git.spec.in
@@ -0,0 +1,188 @@
+# Pass --without docs to rpmbuild if you don't want the documentation
+Name: git
+Version: @@VERSION@@
+Release: 1%{?dist}
+Summary: Git core and tools
+License: GPL
+Group: Development/Tools
+URL: http://kernel.org/pub/software/scm/git/
+Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
+BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk
+
+%description
+This is a stupid (but extremely fast) directory content manager. It
+doesn't do a whole lot, but what it _does_ do is track directory
+contents efficiently. It is intended to be the base of an efficient,
+distributed source code management system. This package includes
+rudimentary tools that can be used as a SCM, but you should look
+elsewhere for tools for ordinary humans layered on top of this.
+
+This is a dummy package which brings in all subpackages.
+
+%package core
+Summary: Core git tools
+Group: Development/Tools
+Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat
+%description core
+This is a stupid (but extremely fast) directory content manager. It
+doesn't do a whole lot, but what it _does_ do is track directory
+contents efficiently. It is intended to be the base of an efficient,
+distributed source code management system. This package includes
+rudimentary tools that can be used as a SCM, but you should look
+elsewhere for tools for ordinary humans layered on top of this.
+
+These are the core tools with minimal dependencies.
+
+%package svn
+Summary: Git tools for importing Subversion repositories
+Group: Development/Tools
+Requires: git-core = %{version}-%{release}, subversion
+%description svn
+Git tools for importing Subversion repositories.
+
+%package cvs
+Summary: Git tools for importing CVS repositories
+Group: Development/Tools
+Requires: git-core = %{version}-%{release}, cvs, cvsps
+%description cvs
+Git tools for importing CVS repositories.
+
+%package arch
+Summary: Git tools for importing Arch repositories
+Group: Development/Tools
+Requires: git-core = %{version}-%{release}, tla
+%description arch
+Git tools for importing Arch repositories.
+
+%package email
+Summary: Git tools for sending email
+Group: Development/Tools
+Requires: git-core = %{version}-%{release}
+%description email
+Git tools for sending email.
+
+%package -n gitk
+Summary: Git revision tree visualiser ('gitk')
+Group: Development/Tools
+Requires: git-core = %{version}-%{release}, tk >= 8.4
+%description -n gitk
+Git revision tree visualiser ('gitk')
+
+%prep
+%setup -q
+
+%build
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease WITH_SEND_EMAIL=1 \
+ prefix=%{_prefix} all %{!?_without_docs: doc}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease WITH_SEND_EMAIL=1 \
+ prefix=%{_prefix} mandir=%{_mandir} \
+ install %{!?_without_docs: install-doc}
+
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files
+%if %{!?_without_docs:1}0
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+# These are no files in the root package
+
+%files svn
+%defattr(-,root,root)
+%{_bindir}/*svn*
+%doc Documentation/*svn*.txt
+%{!?_without_docs: %{_mandir}/man1/*svn*.1*}
+%{!?_without_docs: %doc Documentation/*svn*.html }
+
+%files cvs
+%defattr(-,root,root)
+%doc Documentation/*git-cvs*.txt
+%{_bindir}/*cvs*
+%{!?_without_docs: %{_mandir}/man1/*cvs*.1*}
+%{!?_without_docs: %doc Documentation/*git-cvs*.html }
+
+%files arch
+%defattr(-,root,root)
+%doc Documentation/*arch*.txt
+%{_bindir}/*arch*
+%{!?_without_docs: %{_mandir}/man1/*arch*.1*}
+%{!?_without_docs: %doc Documentation/*arch*.html }
+
+%files email
+%defattr(-,root,root)
+%doc Documentation/*email*.txt
+%{_bindir}/*email*
+%{!?_without_docs: %{_mandir}/man1/*email*.1*}
+%{!?_without_docs: %doc Documentation/*email*.html }
+
+%files -n gitk
+%defattr(-,root,root)
+%doc Documentation/*gitk*.txt
+%{_bindir}/*gitk*
+%{!?_without_docs: %{_mandir}/man1/*gitk*.1*}
+%{!?_without_docs: %doc Documentation/*gitk*.html }
+
+%files core -f bin-man-doc-files
+%defattr(-,root,root)
+%{_datadir}/git-core/
+%doc README COPYING Documentation/*.txt
+%{!?_without_docs: %doc Documentation/*.html }
+
+%changelog
+* Mon Nov 14 2005 H. Peter Anvin <hpa@zytor.com> 0.99.9j-1
+- Change subpackage names to git-<name> instead of git-core-<name>
+- Create empty root package which brings in all subpackages
+- Rename git-tk -> gitk
+
+* Thu Nov 10 2005 Chris Wright <chrisw@osdl.org> 0.99.9g-1
+- zlib dependency fix
+- Minor cleanups from split
+- Move arch import to separate package as well
+
+* Tue Sep 27 2005 Jim Radford <radford@blackbean.org>
+- Move programs with non-standard dependencies (svn, cvs, email)
+ into separate packages
+
+* Tue Sep 27 2005 H. Peter Anvin <hpa@zytor.com>
+- parallelize build
+- COPTS -> CFLAGS
+
+* Fri Sep 16 2005 Chris Wright <chrisw@osdl.org> 0.99.6-1
+- update to 0.99.6
+
+* Fri Sep 16 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+- Linus noticed that less is required, added to the dependencies
+
+* Sun Sep 11 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+- Updated dependencies
+- Don't assume manpages are gzipped
+
+* Thu Aug 18 2005 Chris Wright <chrisw@osdl.org> 0.99.4-4
+- drop sh_utils, sh-utils, diffutils, mktemp, and openssl Requires
+- use RPM_OPT_FLAGS in spec file, drop patch0
+
+* Wed Aug 17 2005 Tom "spot" Callaway <tcallawa@redhat.com> 0.99.4-3
+- use dist tag to differentiate between branches
+- use rpm optflags by default (patch0)
+- own %{_datadir}/git-core/
+
+* Mon Aug 15 2005 Chris Wright <chrisw@osdl.org>
+- update spec file to fix Buildroot, Requires, and drop Vendor
+
+* Sun Aug 07 2005 Horst H. von Brand <vonbrand@inf.utfsm.cl>
+- Redid the description
+- Cut overlong make line, loosened changelog a bit
+- I think Junio (or perhaps OSDL?) should be vendor...
+
+* Thu Jul 14 2005 Eric Biederman <ebiederm@xmission.com>
+- Add the man pages, and the --without docs build option
+
+* Wed Jul 7 2005 Chris Wright <chris@osdl.org>
+- initial git spec file
diff --git a/gitMergeCommon.py b/gitMergeCommon.py
new file mode 100644
index 000000000..ff6f58a07
--- /dev/null
+++ b/gitMergeCommon.py
@@ -0,0 +1,272 @@
+#
+# Copyright (C) 2005 Fredrik Kuivinen
+#
+
+import sys, re, os, traceback
+from sets import Set
+
+def die(*args):
+ printList(args, sys.stderr)
+ sys.exit(2)
+
+def printList(list, file=sys.stdout):
+ for x in list:
+ file.write(str(x))
+ file.write(' ')
+ file.write('\n')
+
+import subprocess
+
+# Debugging machinery
+# -------------------
+
+DEBUG = 0
+functionsToDebug = Set()
+
+def addDebug(func):
+ if type(func) == str:
+ functionsToDebug.add(func)
+ else:
+ functionsToDebug.add(func.func_name)
+
+def debug(*args):
+ if DEBUG:
+ funcName = traceback.extract_stack()[-2][2]
+ if funcName in functionsToDebug:
+ printList(args)
+
+# Program execution
+# -----------------
+
+class ProgramError(Exception):
+ def __init__(self, progStr, error):
+ self.progStr = progStr
+ self.error = error
+
+ def __str__(self):
+ return self.progStr + ': ' + self.error
+
+addDebug('runProgram')
+def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True):
+ debug('runProgram prog:', str(prog), 'input:', str(input))
+ if type(prog) is str:
+ progStr = prog
+ else:
+ progStr = ' '.join(prog)
+
+ try:
+ if pipeOutput:
+ stderr = subprocess.STDOUT
+ stdout = subprocess.PIPE
+ else:
+ stderr = None
+ stdout = None
+ pop = subprocess.Popen(prog,
+ shell = type(prog) is str,
+ stderr=stderr,
+ stdout=stdout,
+ stdin=subprocess.PIPE,
+ env=env)
+ except OSError, e:
+ debug('strerror:', e.strerror)
+ raise ProgramError(progStr, e.strerror)
+
+ if input != None:
+ pop.stdin.write(input)
+ pop.stdin.close()
+
+ if pipeOutput:
+ out = pop.stdout.read()
+ else:
+ out = ''
+
+ code = pop.wait()
+ if returnCode:
+ ret = [out, code]
+ else:
+ ret = out
+ if code != 0 and not returnCode:
+ debug('error output:', out)
+ debug('prog:', prog)
+ raise ProgramError(progStr, out)
+# debug('output:', out.replace('\0', '\n'))
+ return ret
+
+# Code for computing common ancestors
+# -----------------------------------
+
+currentId = 0
+def getUniqueId():
+ global currentId
+ currentId += 1
+ return currentId
+
+# The 'virtual' commit objects have SHAs which are integers
+shaRE = re.compile('^[0-9a-f]{40}$')
+def isSha(obj):
+ return (type(obj) is str and bool(shaRE.match(obj))) or \
+ (type(obj) is int and obj >= 1)
+
+class Commit:
+ def __init__(self, sha, parents, tree=None):
+ self.parents = parents
+ self.firstLineMsg = None
+ self.children = []
+
+ if tree:
+ tree = tree.rstrip()
+ assert(isSha(tree))
+ self._tree = tree
+
+ if not sha:
+ self.sha = getUniqueId()
+ self.virtual = True
+ self.firstLineMsg = 'virtual commit'
+ assert(isSha(tree))
+ else:
+ self.virtual = False
+ self.sha = sha.rstrip()
+ assert(isSha(self.sha))
+
+ def tree(self):
+ self.getInfo()
+ assert(self._tree != None)
+ return self._tree
+
+ def shortInfo(self):
+ self.getInfo()
+ return str(self.sha) + ' ' + self.firstLineMsg
+
+ def __str__(self):
+ return self.shortInfo()
+
+ def getInfo(self):
+ if self.virtual or self.firstLineMsg != None:
+ return
+ else:
+ info = runProgram(['git-cat-file', 'commit', self.sha])
+ info = info.split('\n')
+ msg = False
+ for l in info:
+ if msg:
+ self.firstLineMsg = l
+ break
+ else:
+ if l.startswith('tree'):
+ self._tree = l[5:].rstrip()
+ elif l == '':
+ msg = True
+
+class Graph:
+ def __init__(self):
+ self.commits = []
+ self.shaMap = {}
+
+ def addNode(self, node):
+ assert(isinstance(node, Commit))
+ self.shaMap[node.sha] = node
+ self.commits.append(node)
+ for p in node.parents:
+ p.children.append(node)
+ return node
+
+ def reachableNodes(self, n1, n2):
+ res = {}
+ def traverse(n):
+ res[n] = True
+ for p in n.parents:
+ traverse(p)
+
+ traverse(n1)
+ traverse(n2)
+ return res
+
+ def fixParents(self, node):
+ for x in range(0, len(node.parents)):
+ node.parents[x] = self.shaMap[node.parents[x]]
+
+# addDebug('buildGraph')
+def buildGraph(heads):
+ debug('buildGraph heads:', heads)
+ for h in heads:
+ assert(isSha(h))
+
+ g = Graph()
+
+ out = runProgram(['git-rev-list', '--parents'] + heads)
+ for l in out.split('\n'):
+ if l == '':
+ continue
+ shas = l.split(' ')
+
+ # This is a hack, we temporarily use the 'parents' attribute
+ # to contain a list of SHA1:s. They are later replaced by proper
+ # Commit objects.
+ c = Commit(shas[0], shas[1:])
+
+ g.commits.append(c)
+ g.shaMap[c.sha] = c
+
+ for c in g.commits:
+ g.fixParents(c)
+
+ for c in g.commits:
+ for p in c.parents:
+ p.children.append(c)
+ return g
+
+# Write the empty tree to the object database and return its SHA1
+def writeEmptyTree():
+ tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index'
+ def delTmpIndex():
+ try:
+ os.unlink(tmpIndex)
+ except OSError:
+ pass
+ delTmpIndex()
+ newEnv = os.environ.copy()
+ newEnv['GIT_INDEX_FILE'] = tmpIndex
+ res = runProgram(['git-write-tree'], env=newEnv).rstrip()
+ delTmpIndex()
+ return res
+
+def addCommonRoot(graph):
+ roots = []
+ for c in graph.commits:
+ if len(c.parents) == 0:
+ roots.append(c)
+
+ superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree())
+ graph.addNode(superRoot)
+ for r in roots:
+ r.parents = [superRoot]
+ superRoot.children = roots
+ return superRoot
+
+def getCommonAncestors(graph, commit1, commit2):
+ '''Find the common ancestors for commit1 and commit2'''
+ assert(isinstance(commit1, Commit) and isinstance(commit2, Commit))
+
+ def traverse(start, set):
+ stack = [start]
+ while len(stack) > 0:
+ el = stack.pop()
+ set.add(el)
+ for p in el.parents:
+ if p not in set:
+ stack.append(p)
+ h1Set = Set()
+ h2Set = Set()
+ traverse(commit1, h1Set)
+ traverse(commit2, h2Set)
+ shared = h1Set.intersection(h2Set)
+
+ if len(shared) == 0:
+ shared = [addCommonRoot(graph)]
+
+ res = Set()
+
+ for s in shared:
+ if len([c for c in s.children if c in shared]) == 0:
+ res.add(s)
+ return list(res)
diff --git a/hash-object.c b/hash-object.c
new file mode 100644
index 000000000..c8c9adb3a
--- /dev/null
+++ b/hash-object.c
@@ -0,0 +1,45 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Junio C Hamano, 2005
+ */
+#include "cache.h"
+
+static void hash_object(const char *path, const char *type, int write_object)
+{
+ int fd;
+ struct stat st;
+ unsigned char sha1[20];
+ fd = open(path, O_RDONLY);
+ if (fd < 0 ||
+ fstat(fd, &st) < 0 ||
+ index_fd(sha1, fd, &st, write_object, type))
+ die(write_object
+ ? "Unable to add %s to database"
+ : "Unable to hash %s", path);
+ printf("%s\n", sha1_to_hex(sha1));
+}
+
+static const char hash_object_usage[] =
+"git-hash-object [-t <type>] [-w] <file>...";
+
+int main(int argc, char **argv)
+{
+ int i;
+ const char *type = "blob";
+ int write_object = 0;
+
+ for (i = 1 ; i < argc; i++) {
+ if (!strcmp(argv[i], "-t")) {
+ if (argc <= ++i)
+ die(hash_object_usage);
+ type = argv[i];
+ }
+ else if (!strcmp(argv[i], "-w"))
+ write_object = 1;
+ else
+ hash_object(argv[i], type, write_object);
+ }
+ return 0;
+}
diff --git a/http-fetch.c b/http-fetch.c
new file mode 100644
index 000000000..21cc1b960
--- /dev/null
+++ b/http-fetch.c
@@ -0,0 +1,1426 @@
+#include "cache.h"
+#include "commit.h"
+#include "pack.h"
+#include "fetch.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#if LIBCURL_VERSION_NUM >= 0x070908
+#define USE_CURL_MULTI
+#define DEFAULT_MAX_REQUESTS 5
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070704
+#define curl_global_cleanup() do { /* nothing */ } while(0)
+#endif
+#if LIBCURL_VERSION_NUM < 0x070800
+#define curl_global_init(a) do { /* nothing */ } while(0)
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070c04
+#define NO_CURL_EASY_DUPHANDLE
+#endif
+
+#define PREV_BUF_SIZE 4096
+#define RANGE_HEADER_SIZE 30
+
+static int got_alternates = -1;
+static int active_requests = 0;
+static int data_received;
+
+#ifdef USE_CURL_MULTI
+static int max_requests = -1;
+static CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+static CURL *curl_default;
+#endif
+static struct curl_slist *pragma_header;
+static struct curl_slist *no_pragma_header;
+static struct curl_slist *no_range_header;
+static char curl_errorstr[CURL_ERROR_SIZE];
+
+struct alt_base
+{
+ char *base;
+ int got_indices;
+ struct packed_git *packs;
+ struct alt_base *next;
+};
+
+static struct alt_base *alt = NULL;
+
+enum transfer_state {
+ WAITING,
+ ABORTED,
+ ACTIVE,
+ COMPLETE,
+};
+
+struct transfer_request
+{
+ unsigned char sha1[20];
+ struct alt_base *repo;
+ char *url;
+ char filename[PATH_MAX];
+ char tmpfile[PATH_MAX];
+ int local;
+ enum transfer_state state;
+ CURLcode curl_result;
+ char errorstr[CURL_ERROR_SIZE];
+ long http_code;
+ unsigned char real_sha1[20];
+ SHA_CTX c;
+ z_stream stream;
+ int zret;
+ int rename;
+ struct active_request_slot *slot;
+ struct transfer_request *next;
+};
+
+struct active_request_slot
+{
+ CURL *curl;
+ FILE *local;
+ int in_use;
+ int done;
+ CURLcode curl_result;
+ long http_code;
+ void *callback_data;
+ void (*callback_func)(void *data);
+ struct active_request_slot *next;
+};
+
+struct alt_request {
+ char *base;
+ char *url;
+ struct buffer *buffer;
+ struct active_request_slot *slot;
+ int http_specific;
+};
+
+static struct transfer_request *request_queue_head = NULL;
+static struct active_request_slot *active_queue_head = NULL;
+
+static int curl_ssl_verify = -1;
+static char *ssl_cert = NULL;
+#if LIBCURL_VERSION_NUM >= 0x070902
+static char *ssl_key = NULL;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+static char *ssl_capath = NULL;
+#endif
+static char *ssl_cainfo = NULL;
+static long curl_low_speed_limit = -1;
+static long curl_low_speed_time = -1;
+
+struct buffer
+{
+ size_t posn;
+ size_t size;
+ void *buffer;
+};
+
+static int http_options(const char *var, const char *value)
+{
+ if (!strcmp("http.sslverify", var)) {
+ if (curl_ssl_verify == -1) {
+ curl_ssl_verify = git_config_bool(var, value);
+ }
+ return 0;
+ }
+
+ if (!strcmp("http.sslcert", var)) {
+ if (ssl_cert == NULL) {
+ ssl_cert = xmalloc(strlen(value)+1);
+ strcpy(ssl_cert, value);
+ }
+ return 0;
+ }
+#if LIBCURL_VERSION_NUM >= 0x070902
+ if (!strcmp("http.sslkey", var)) {
+ if (ssl_key == NULL) {
+ ssl_key = xmalloc(strlen(value)+1);
+ strcpy(ssl_key, value);
+ }
+ return 0;
+ }
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+ if (!strcmp("http.sslcapath", var)) {
+ if (ssl_capath == NULL) {
+ ssl_capath = xmalloc(strlen(value)+1);
+ strcpy(ssl_capath, value);
+ }
+ return 0;
+ }
+#endif
+ if (!strcmp("http.sslcainfo", var)) {
+ if (ssl_cainfo == NULL) {
+ ssl_cainfo = xmalloc(strlen(value)+1);
+ strcpy(ssl_cainfo, value);
+ }
+ return 0;
+ }
+
+#ifdef USE_CURL_MULTI
+ if (!strcmp("http.maxrequests", var)) {
+ if (max_requests == -1)
+ max_requests = git_config_int(var, value);
+ return 0;
+ }
+#endif
+
+ if (!strcmp("http.lowspeedlimit", var)) {
+ if (curl_low_speed_limit == -1)
+ curl_low_speed_limit = (long)git_config_int(var, value);
+ return 0;
+ }
+ if (!strcmp("http.lowspeedtime", var)) {
+ if (curl_low_speed_time == -1)
+ curl_low_speed_time = (long)git_config_int(var, value);
+ return 0;
+ }
+
+ /* Fall back on the default ones */
+ return git_default_config(var, value);
+}
+
+static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
+ struct buffer *buffer)
+{
+ size_t size = eltsize * nmemb;
+ if (size > buffer->size - buffer->posn)
+ size = buffer->size - buffer->posn;
+ memcpy(buffer->buffer + buffer->posn, ptr, size);
+ buffer->posn += size;
+ data_received++;
+ return size;
+}
+
+static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
+ size_t nmemb, struct buffer *buffer)
+{
+ size_t size = eltsize * nmemb;
+ if (size > buffer->size - buffer->posn) {
+ buffer->size = buffer->size * 3 / 2;
+ if (buffer->size < buffer->posn + size)
+ buffer->size = buffer->posn + size;
+ buffer->buffer = xrealloc(buffer->buffer, buffer->size);
+ }
+ memcpy(buffer->buffer + buffer->posn, ptr, size);
+ buffer->posn += size;
+ data_received++;
+ return size;
+}
+
+static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
+ void *data)
+{
+ unsigned char expn[4096];
+ size_t size = eltsize * nmemb;
+ int posn = 0;
+ struct transfer_request *request = (struct transfer_request *)data;
+ do {
+ ssize_t retval = write(request->local,
+ ptr + posn, size - posn);
+ if (retval < 0)
+ return posn;
+ posn += retval;
+ } while (posn < size);
+
+ request->stream.avail_in = size;
+ request->stream.next_in = ptr;
+ do {
+ request->stream.next_out = expn;
+ request->stream.avail_out = sizeof(expn);
+ request->zret = inflate(&request->stream, Z_SYNC_FLUSH);
+ SHA1_Update(&request->c, expn,
+ sizeof(expn) - request->stream.avail_out);
+ } while (request->stream.avail_in && request->zret == Z_OK);
+ data_received++;
+ return size;
+}
+
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void);
+static void process_request_queue(void);
+#endif
+static void fetch_alternates(char *base);
+
+static CURL* get_curl_handle(void)
+{
+ CURL* result = curl_easy_init();
+
+ curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
+#if LIBCURL_VERSION_NUM >= 0x070907
+ curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+#endif
+
+ if (ssl_cert != NULL)
+ curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
+#if LIBCURL_VERSION_NUM >= 0x070902
+ if (ssl_key != NULL)
+ curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+ if (ssl_capath != NULL)
+ curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
+#endif
+ if (ssl_cainfo != NULL)
+ curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+ curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
+
+ if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
+ curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
+ curl_low_speed_limit);
+ curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
+ curl_low_speed_time);
+ }
+
+ curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
+
+ return result;
+}
+
+static struct active_request_slot *get_active_slot(void)
+{
+ struct active_request_slot *slot = active_queue_head;
+ struct active_request_slot *newslot;
+
+#ifdef USE_CURL_MULTI
+ int num_transfers;
+
+ /* Wait for a slot to open up if the queue is full */
+ while (active_requests >= max_requests) {
+ curl_multi_perform(curlm, &num_transfers);
+ if (num_transfers < active_requests) {
+ process_curl_messages();
+ }
+ }
+#endif
+
+ while (slot != NULL && slot->in_use) {
+ slot = slot->next;
+ }
+ if (slot == NULL) {
+ newslot = xmalloc(sizeof(*newslot));
+ newslot->curl = NULL;
+ newslot->in_use = 0;
+ newslot->next = NULL;
+
+ slot = active_queue_head;
+ if (slot == NULL) {
+ active_queue_head = newslot;
+ } else {
+ while (slot->next != NULL) {
+ slot = slot->next;
+ }
+ slot->next = newslot;
+ }
+ slot = newslot;
+ }
+
+ if (slot->curl == NULL) {
+#ifdef NO_CURL_EASY_DUPHANDLE
+ slot->curl = get_curl_handle();
+#else
+ slot->curl = curl_easy_duphandle(curl_default);
+#endif
+ }
+
+ active_requests++;
+ slot->in_use = 1;
+ slot->done = 0;
+ slot->local = NULL;
+ slot->callback_data = NULL;
+ slot->callback_func = NULL;
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_range_header);
+ curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+
+ return slot;
+}
+
+static int start_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+ CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+
+ if (curlm_result != CURLM_OK &&
+ curlm_result != CURLM_CALL_MULTI_PERFORM) {
+ active_requests--;
+ slot->in_use = 0;
+ return 0;
+ }
+#endif
+ return 1;
+}
+
+static void run_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+ int num_transfers;
+ long last_pos = 0;
+ long current_pos;
+ fd_set readfds;
+ fd_set writefds;
+ fd_set excfds;
+ int max_fd;
+ struct timeval select_timeout;
+ CURLMcode curlm_result;
+
+ while (!slot->done) {
+ data_received = 0;
+ do {
+ curlm_result = curl_multi_perform(curlm,
+ &num_transfers);
+ } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
+ if (num_transfers < active_requests) {
+ process_curl_messages();
+ process_request_queue();
+ }
+
+ if (!data_received && slot->local != NULL) {
+ current_pos = ftell(slot->local);
+ if (current_pos > last_pos)
+ data_received++;
+ last_pos = current_pos;
+ }
+
+ if (!slot->done && !data_received) {
+ max_fd = 0;
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&excfds);
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 50000;
+ select(max_fd, &readfds, &writefds,
+ &excfds, &select_timeout);
+ }
+ }
+#else
+ slot->curl_result = curl_easy_perform(slot->curl);
+ active_requests--;
+#endif
+}
+
+static void start_request(struct transfer_request *request)
+{
+ char *hex = sha1_to_hex(request->sha1);
+ char prevfile[PATH_MAX];
+ char *url;
+ char *posn;
+ int prevlocal;
+ unsigned char prev_buf[PREV_BUF_SIZE];
+ ssize_t prev_read = 0;
+ long prev_posn = 0;
+ char range[RANGE_HEADER_SIZE];
+ struct curl_slist *range_header = NULL;
+ struct active_request_slot *slot;
+
+ snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
+ unlink(prevfile);
+ rename(request->tmpfile, prevfile);
+ unlink(request->tmpfile);
+
+ if (request->local != -1)
+ error("fd leakage in start: %d", request->local);
+ request->local = open(request->tmpfile,
+ O_WRONLY | O_CREAT | O_EXCL, 0666);
+ /* This could have failed due to the "lazy directory creation";
+ * try to mkdir the last path component.
+ */
+ if (request->local < 0 && errno == ENOENT) {
+ char *dir = strrchr(request->tmpfile, '/');
+ if (dir) {
+ *dir = 0;
+ mkdir(request->tmpfile, 0777);
+ *dir = '/';
+ }
+ request->local = open(request->tmpfile,
+ O_WRONLY | O_CREAT | O_EXCL, 0666);
+ }
+
+ if (request->local < 0) {
+ request->state = ABORTED;
+ error("Couldn't create temporary file %s for %s: %s\n",
+ request->tmpfile, request->filename, strerror(errno));
+ return;
+ }
+
+ memset(&request->stream, 0, sizeof(request->stream));
+
+ inflateInit(&request->stream);
+
+ SHA1_Init(&request->c);
+
+ url = xmalloc(strlen(request->repo->base) + 50);
+ request->url = xmalloc(strlen(request->repo->base) + 50);
+ strcpy(url, request->repo->base);
+ posn = url + strlen(request->repo->base);
+ strcpy(posn, "objects/");
+ posn += 8;
+ memcpy(posn, hex, 2);
+ posn += 2;
+ *(posn++) = '/';
+ strcpy(posn, hex + 2);
+ strcpy(request->url, url);
+
+ /* If a previous temp file is present, process what was already
+ fetched. */
+ prevlocal = open(prevfile, O_RDONLY);
+ if (prevlocal != -1) {
+ do {
+ prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+ if (prev_read>0) {
+ if (fwrite_sha1_file(prev_buf,
+ 1,
+ prev_read,
+ request) == prev_read) {
+ prev_posn += prev_read;
+ } else {
+ prev_read = -1;
+ }
+ }
+ } while (prev_read > 0);
+ close(prevlocal);
+ }
+ unlink(prevfile);
+
+ /* Reset inflate/SHA1 if there was an error reading the previous temp
+ file; also rewind to the beginning of the local file. */
+ if (prev_read == -1) {
+ memset(&request->stream, 0, sizeof(request->stream));
+ inflateInit(&request->stream);
+ SHA1_Init(&request->c);
+ if (prev_posn>0) {
+ prev_posn = 0;
+ lseek(request->local, SEEK_SET, 0);
+ ftruncate(request->local, 0);
+ }
+ }
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, request);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
+ curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+
+ /* If we have successfully processed data from a previous fetch
+ attempt, only fetch the data we don't already have. */
+ if (prev_posn>0) {
+ if (get_verbosely)
+ fprintf(stderr,
+ "Resuming fetch of object %s at byte %ld\n",
+ hex, prev_posn);
+ sprintf(range, "Range: bytes=%ld-", prev_posn);
+ range_header = curl_slist_append(range_header, range);
+ curl_easy_setopt(slot->curl,
+ CURLOPT_HTTPHEADER, range_header);
+ }
+
+ /* Try to get the request started, abort the request on error */
+ if (!start_active_slot(slot)) {
+ request->state = ABORTED;
+ close(request->local); request->local = -1;
+ free(request->url);
+ return;
+ }
+
+ request->slot = slot;
+ request->state = ACTIVE;
+}
+
+static void finish_request(struct transfer_request *request)
+{
+ struct stat st;
+
+ fchmod(request->local, 0444);
+ close(request->local); request->local = -1;
+
+ if (request->http_code == 416) {
+ fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+ } else if (request->curl_result != CURLE_OK) {
+ if (stat(request->tmpfile, &st) == 0)
+ if (st.st_size == 0)
+ unlink(request->tmpfile);
+ return;
+ }
+
+ inflateEnd(&request->stream);
+ SHA1_Final(request->real_sha1, &request->c);
+ if (request->zret != Z_STREAM_END) {
+ unlink(request->tmpfile);
+ return;
+ }
+ if (memcmp(request->sha1, request->real_sha1, 20)) {
+ unlink(request->tmpfile);
+ return;
+ }
+ request->rename =
+ move_temp_to_file(request->tmpfile, request->filename);
+
+ if (request->rename == 0)
+ pull_say("got %s\n", sha1_to_hex(request->sha1));
+}
+
+static void release_request(struct transfer_request *request)
+{
+ struct transfer_request *entry = request_queue_head;
+
+ if (request->local != -1)
+ error("fd leakage in release: %d", request->local);
+ if (request == request_queue_head) {
+ request_queue_head = request->next;
+ } else {
+ while (entry->next != NULL && entry->next != request)
+ entry = entry->next;
+ if (entry->next == request)
+ entry->next = entry->next->next;
+ }
+
+ free(request->url);
+ free(request);
+}
+
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void)
+{
+ int num_messages;
+ struct active_request_slot *slot;
+ struct transfer_request *request = NULL;
+ CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
+
+ while (curl_message != NULL) {
+ if (curl_message->msg == CURLMSG_DONE) {
+ int curl_result = curl_message->data.result;
+ slot = active_queue_head;
+ while (slot != NULL &&
+ slot->curl != curl_message->easy_handle)
+ slot = slot->next;
+ if (slot != NULL) {
+ curl_multi_remove_handle(curlm, slot->curl);
+ active_requests--;
+ slot->done = 1;
+ slot->in_use = 0;
+ slot->curl_result = curl_result;
+ curl_easy_getinfo(slot->curl,
+ CURLINFO_HTTP_CODE,
+ &slot->http_code);
+ request = request_queue_head;
+ while (request != NULL &&
+ request->slot != slot)
+ request = request->next;
+ } else {
+ fprintf(stderr, "Received DONE message for unknown request!\n");
+ }
+
+ /* Process slot callback if appropriate */
+ if (slot->callback_func != NULL) {
+ slot->callback_func(slot->callback_data);
+ }
+
+ if (request != NULL) {
+ request->curl_result = curl_result;
+ request->http_code = slot->http_code;
+ request->slot = NULL;
+ request->state = COMPLETE;
+
+ /* Use alternates if necessary */
+ if (request->http_code == 404) {
+ fetch_alternates(alt->base);
+ if (request->repo->next != NULL) {
+ request->repo =
+ request->repo->next;
+ close(request->local);
+ request->local = -1;
+ start_request(request);
+ } else {
+ finish_request(request);
+ }
+ } else {
+ finish_request(request);
+ }
+ }
+ } else {
+ fprintf(stderr, "Unknown CURL message received: %d\n",
+ (int)curl_message->msg);
+ }
+ curl_message = curl_multi_info_read(curlm, &num_messages);
+ }
+}
+
+static void process_request_queue(void)
+{
+ struct transfer_request *request = request_queue_head;
+ struct active_request_slot *slot = active_queue_head;
+ int num_transfers;
+
+ while (active_requests < max_requests && request != NULL) {
+ if (request->state == WAITING) {
+ if (has_sha1_file(request->sha1))
+ release_request(request);
+ else
+ start_request(request);
+ curl_multi_perform(curlm, &num_transfers);
+ }
+ request = request->next;
+ }
+
+ while (slot != NULL) {
+ if (!slot->in_use && slot->curl != NULL) {
+ curl_easy_cleanup(slot->curl);
+ slot->curl = NULL;
+ }
+ slot = slot->next;
+ }
+}
+#endif
+
+void prefetch(unsigned char *sha1)
+{
+ struct transfer_request *newreq;
+ struct transfer_request *tail;
+ char *filename = sha1_file_name(sha1);
+
+ newreq = xmalloc(sizeof(*newreq));
+ memcpy(newreq->sha1, sha1, 20);
+ newreq->repo = alt;
+ newreq->url = NULL;
+ newreq->local = -1;
+ newreq->state = WAITING;
+ snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
+ snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
+ "%s.temp", filename);
+ newreq->next = NULL;
+
+ if (request_queue_head == NULL) {
+ request_queue_head = newreq;
+ } else {
+ tail = request_queue_head;
+ while (tail->next != NULL) {
+ tail = tail->next;
+ }
+ tail->next = newreq;
+ }
+#ifdef USE_CURL_MULTI
+ process_request_queue();
+ process_curl_messages();
+#endif
+}
+
+static int fetch_index(struct alt_base *repo, unsigned char *sha1)
+{
+ char *hex = sha1_to_hex(sha1);
+ char *filename;
+ char *url;
+ char tmpfile[PATH_MAX];
+ long prev_posn = 0;
+ char range[RANGE_HEADER_SIZE];
+ struct curl_slist *range_header = NULL;
+
+ FILE *indexfile;
+ struct active_request_slot *slot;
+
+ if (has_pack_index(sha1))
+ return 0;
+
+ if (get_verbosely)
+ fprintf(stderr, "Getting index for pack %s\n", hex);
+
+ url = xmalloc(strlen(repo->base) + 64);
+ sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
+
+ filename = sha1_pack_index_name(sha1);
+ snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+ indexfile = fopen(tmpfile, "a");
+ if (!indexfile)
+ return error("Unable to open local file %s for pack index",
+ filename);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+ slot->local = indexfile;
+
+ /* If there is data present from a previous transfer attempt,
+ resume where it left off */
+ prev_posn = ftell(indexfile);
+ if (prev_posn>0) {
+ if (get_verbosely)
+ fprintf(stderr,
+ "Resuming fetch of index for pack %s at byte %ld\n",
+ hex, prev_posn);
+ sprintf(range, "Range: bytes=%ld-", prev_posn);
+ range_header = curl_slist_append(range_header, range);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+ }
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK) {
+ fclose(indexfile);
+ return error("Unable to get pack index %s\n%s", url,
+ curl_errorstr);
+ }
+ } else {
+ fclose(indexfile);
+ return error("Unable to start request");
+ }
+
+ fclose(indexfile);
+
+ return move_temp_to_file(tmpfile, filename);
+}
+
+static int setup_index(struct alt_base *repo, unsigned char *sha1)
+{
+ struct packed_git *new_pack;
+ if (has_pack_file(sha1))
+ return 0; // don't list this as something we can get
+
+ if (fetch_index(repo, sha1))
+ return -1;
+
+ new_pack = parse_pack_index(sha1);
+ new_pack->next = repo->packs;
+ repo->packs = new_pack;
+ return 0;
+}
+
+static void process_alternates(void *callback_data)
+{
+ struct alt_request *alt_req = (struct alt_request *)callback_data;
+ struct active_request_slot *slot = alt_req->slot;
+ struct alt_base *tail = alt;
+ char *base = alt_req->base;
+ static const char null_byte = '\0';
+ char *data;
+ int i = 0;
+
+ if (alt_req->http_specific) {
+ if (slot->curl_result != CURLE_OK ||
+ !alt_req->buffer->posn) {
+
+ /* Try reusing the slot to get non-http alternates */
+ alt_req->http_specific = 0;
+ sprintf(alt_req->url, "%s/objects/info/alternates",
+ base);
+ curl_easy_setopt(slot->curl, CURLOPT_URL,
+ alt_req->url);
+ active_requests++;
+ slot->in_use = 1;
+ slot->done = 0;
+ if (start_active_slot(slot)) {
+ return;
+ } else {
+ got_alternates = -1;
+ slot->done = 1;
+ return;
+ }
+ }
+ } else if (slot->curl_result != CURLE_OK) {
+ if (slot->http_code != 404) {
+ got_alternates = -1;
+ return;
+ }
+ }
+
+ fwrite_buffer_dynamic(&null_byte, 1, 1, alt_req->buffer);
+ alt_req->buffer->posn--;
+ data = alt_req->buffer->buffer;
+
+ while (i < alt_req->buffer->posn) {
+ int posn = i;
+ while (posn < alt_req->buffer->posn && data[posn] != '\n')
+ posn++;
+ if (data[posn] == '\n') {
+ int okay = 0;
+ int serverlen = 0;
+ struct alt_base *newalt;
+ char *target = NULL;
+ if (data[i] == '/') {
+ serverlen = strchr(base + 8, '/') - base;
+ okay = 1;
+ } else if (!memcmp(data + i, "../", 3)) {
+ i += 3;
+ serverlen = strlen(base);
+ while (i + 2 < posn &&
+ !memcmp(data + i, "../", 3)) {
+ do {
+ serverlen--;
+ } while (serverlen &&
+ base[serverlen - 1] != '/');
+ i += 3;
+ }
+ // If the server got removed, give up.
+ okay = strchr(base, ':') - base + 3 <
+ serverlen;
+ } else if (alt_req->http_specific) {
+ char *colon = strchr(data + i, ':');
+ char *slash = strchr(data + i, '/');
+ if (colon && slash && colon < data + posn &&
+ slash < data + posn && colon < slash) {
+ okay = 1;
+ }
+ }
+ // skip 'objects' at end
+ if (okay) {
+ target = xmalloc(serverlen + posn - i - 6);
+ strncpy(target, base, serverlen);
+ strncpy(target + serverlen, data + i,
+ posn - i - 7);
+ target[serverlen + posn - i - 7] = '\0';
+ if (get_verbosely)
+ fprintf(stderr,
+ "Also look at %s\n", target);
+ newalt = xmalloc(sizeof(*newalt));
+ newalt->next = NULL;
+ newalt->base = target;
+ newalt->got_indices = 0;
+ newalt->packs = NULL;
+ while (tail->next != NULL)
+ tail = tail->next;
+ tail->next = newalt;
+ }
+ }
+ i = posn + 1;
+ }
+
+ got_alternates = 1;
+}
+
+static void fetch_alternates(char *base)
+{
+ struct buffer buffer;
+ char *url;
+ char *data;
+ struct active_request_slot *slot;
+ static struct alt_request alt_req;
+ int num_transfers;
+
+ /* If another request has already started fetching alternates,
+ wait for them to arrive and return to processing this request's
+ curl message */
+ while (got_alternates == 0) {
+ curl_multi_perform(curlm, &num_transfers);
+ process_curl_messages();
+ process_request_queue();
+ }
+
+ /* Nothing to do if they've already been fetched */
+ if (got_alternates == 1)
+ return;
+
+ /* Start the fetch */
+ got_alternates = 0;
+
+ data = xmalloc(4096);
+ buffer.size = 4096;
+ buffer.posn = 0;
+ buffer.buffer = data;
+
+ if (get_verbosely)
+ fprintf(stderr, "Getting alternates list for %s\n", base);
+
+ url = xmalloc(strlen(base) + 31);
+ sprintf(url, "%s/objects/info/http-alternates", base);
+
+ /* Use a callback to process the result, since another request
+ may fail and need to have alternates loaded before continuing */
+ slot = get_active_slot();
+ slot->callback_func = process_alternates;
+ slot->callback_data = &alt_req;
+
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+ fwrite_buffer_dynamic);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+
+ alt_req.base = base;
+ alt_req.url = url;
+ alt_req.buffer = &buffer;
+ alt_req.http_specific = 1;
+ alt_req.slot = slot;
+
+ if (start_active_slot(slot))
+ run_active_slot(slot);
+ else
+ got_alternates = -1;
+
+ free(data);
+ free(url);
+}
+
+static int fetch_indices(struct alt_base *repo)
+{
+ unsigned char sha1[20];
+ char *url;
+ struct buffer buffer;
+ char *data;
+ int i = 0;
+
+ struct active_request_slot *slot;
+
+ if (repo->got_indices)
+ return 0;
+
+ data = xmalloc(4096);
+ buffer.size = 4096;
+ buffer.posn = 0;
+ buffer.buffer = data;
+
+ if (get_verbosely)
+ fprintf(stderr, "Getting pack list for %s\n", repo->base);
+
+ url = xmalloc(strlen(repo->base) + 21);
+ sprintf(url, "%s/objects/info/packs", repo->base);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+ fwrite_buffer_dynamic);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK) {
+ free(buffer.buffer);
+ return error("%s", curl_errorstr);
+ }
+ } else {
+ free(buffer.buffer);
+ return error("Unable to start request");
+ }
+
+ data = buffer.buffer;
+ while (i < buffer.posn) {
+ switch (data[i]) {
+ case 'P':
+ i++;
+ if (i + 52 < buffer.posn &&
+ !strncmp(data + i, " pack-", 6) &&
+ !strncmp(data + i + 46, ".pack\n", 6)) {
+ get_sha1_hex(data + i + 6, sha1);
+ setup_index(repo, sha1);
+ i += 51;
+ break;
+ }
+ default:
+ while (data[i] != '\n')
+ i++;
+ }
+ i++;
+ }
+
+ free(buffer.buffer);
+ repo->got_indices = 1;
+ return 0;
+}
+
+static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
+{
+ char *url;
+ struct packed_git *target;
+ struct packed_git **lst;
+ FILE *packfile;
+ char *filename;
+ char tmpfile[PATH_MAX];
+ int ret;
+ long prev_posn = 0;
+ char range[RANGE_HEADER_SIZE];
+ struct curl_slist *range_header = NULL;
+
+ struct active_request_slot *slot;
+
+ if (fetch_indices(repo))
+ return -1;
+ target = find_sha1_pack(sha1, repo->packs);
+ if (!target)
+ return -1;
+
+ if (get_verbosely) {
+ fprintf(stderr, "Getting pack %s\n",
+ sha1_to_hex(target->sha1));
+ fprintf(stderr, " which contains %s\n",
+ sha1_to_hex(sha1));
+ }
+
+ url = xmalloc(strlen(repo->base) + 65);
+ sprintf(url, "%s/objects/pack/pack-%s.pack",
+ repo->base, sha1_to_hex(target->sha1));
+
+ filename = sha1_pack_name(target->sha1);
+ snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+ packfile = fopen(tmpfile, "a");
+ if (!packfile)
+ return error("Unable to open local file %s for pack",
+ filename);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+ slot->local = packfile;
+
+ /* If there is data present from a previous transfer attempt,
+ resume where it left off */
+ prev_posn = ftell(packfile);
+ if (prev_posn>0) {
+ if (get_verbosely)
+ fprintf(stderr,
+ "Resuming fetch of pack %s at byte %ld\n",
+ sha1_to_hex(target->sha1), prev_posn);
+ sprintf(range, "Range: bytes=%ld-", prev_posn);
+ range_header = curl_slist_append(range_header, range);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+ }
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK) {
+ fclose(packfile);
+ return error("Unable to get pack file %s\n%s", url,
+ curl_errorstr);
+ }
+ } else {
+ fclose(packfile);
+ return error("Unable to start request");
+ }
+
+ fclose(packfile);
+
+ ret = move_temp_to_file(tmpfile, filename);
+ if (ret)
+ return ret;
+
+ lst = &repo->packs;
+ while (*lst != target)
+ lst = &((*lst)->next);
+ *lst = (*lst)->next;
+
+ if (verify_pack(target, 0))
+ return -1;
+ install_packed_git(target);
+
+ return 0;
+}
+
+static int fetch_object(struct alt_base *repo, unsigned char *sha1)
+{
+ char *hex = sha1_to_hex(sha1);
+ int ret;
+ struct transfer_request *request = request_queue_head;
+
+ while (request != NULL && memcmp(request->sha1, sha1, 20))
+ request = request->next;
+ if (request == NULL)
+ return error("Couldn't find request for %s in the queue", hex);
+
+ if (has_sha1_file(request->sha1)) {
+ release_request(request);
+ return 0;
+ }
+
+#ifdef USE_CURL_MULTI
+ while (request->state == WAITING) {
+ int num_transfers;
+ curl_multi_perform(curlm, &num_transfers);
+ if (num_transfers < active_requests) {
+ process_curl_messages();
+ process_request_queue();
+ }
+ }
+#else
+ start_request(request);
+#endif
+
+ while (request->state == ACTIVE) {
+ run_active_slot(request->slot);
+#ifndef USE_CURL_MULTI
+ request->curl_result = request->slot->curl_result;
+ request->http_code = request->slot->http_code;
+ request->slot = NULL;
+
+ /* Use alternates if necessary */
+ if (request->http_code == 404) {
+ fetch_alternates(alt->base);
+ if (request->repo->next != NULL) {
+ request->repo = request->repo->next;
+ close(request->local); request->local = -1;
+ start_request(request);
+ }
+ } else {
+ finish_request(request);
+ request->state = COMPLETE;
+ }
+#endif
+ }
+ if (request->local != -1) {
+ close(request->local); request->local = -1;
+ }
+
+ if (request->state == ABORTED) {
+ release_request(request);
+ return error("Request for %s aborted", hex);
+ }
+
+ if (request->curl_result != CURLE_OK && request->http_code != 416) {
+ if (request->http_code == 404)
+ ret = -1; /* Be silent, it is probably in a pack. */
+ else
+ ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
+ request->errorstr, request->curl_result,
+ request->http_code, hex);
+ release_request(request);
+ return ret;
+ }
+
+ if (request->zret != Z_STREAM_END) {
+ ret = error("File %s (%s) corrupt\n", hex, request->url);
+ release_request(request);
+ return ret;
+ }
+
+ if (memcmp(request->sha1, request->real_sha1, 20)) {
+ release_request(request);
+ return error("File %s has bad hash\n", hex);
+ }
+
+ if (request->rename < 0) {
+ ret = error("unable to write sha1 filename %s: %s",
+ request->filename,
+ strerror(request->rename));
+ release_request(request);
+ return ret;
+ }
+
+ release_request(request);
+ return 0;
+}
+
+int fetch(unsigned char *sha1)
+{
+ struct alt_base *altbase = alt;
+
+ if (!fetch_object(altbase, sha1))
+ return 0;
+ while (altbase) {
+ if (!fetch_pack(altbase, sha1))
+ return 0;
+ fetch_alternates(alt->base);
+ altbase = altbase->next;
+ }
+ return error("Unable to find %s under %s\n", sha1_to_hex(sha1),
+ alt->base);
+}
+
+static inline int needs_quote(int ch)
+{
+ switch (ch) {
+ case '/': case '-': case '.':
+ case 'A'...'Z': case 'a'...'z': case '0'...'9':
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+static inline int hex(int v)
+{
+ if (v < 10) return '0' + v;
+ else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+ const char *cp;
+ char *dp, *qref;
+ int len, baselen, ch;
+
+ baselen = strlen(base);
+ len = baselen + 6; /* "refs/" + NUL */
+ for (cp = ref; (ch = *cp) != 0; cp++, len++)
+ if (needs_quote(ch))
+ len += 2; /* extra two hex plus replacement % */
+ qref = xmalloc(len);
+ memcpy(qref, base, baselen);
+ memcpy(qref + baselen, "refs/", 5);
+ for (cp = ref, dp = qref + baselen + 5; (ch = *cp) != 0; cp++) {
+ if (needs_quote(ch)) {
+ *dp++ = '%';
+ *dp++ = hex((ch >> 4) & 0xF);
+ *dp++ = hex(ch & 0xF);
+ }
+ else
+ *dp++ = ch;
+ }
+ *dp = 0;
+
+ return qref;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+ char *url;
+ char hex[42];
+ struct buffer buffer;
+ char *base = alt->base;
+ struct active_request_slot *slot;
+ buffer.size = 41;
+ buffer.posn = 0;
+ buffer.buffer = hex;
+ hex[41] = '\0';
+
+ url = quote_ref_url(base, ref);
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK)
+ return error("Couldn't get %s for %s\n%s",
+ url, ref, curl_errorstr);
+ } else {
+ return error("Unable to start request");
+ }
+
+ hex[40] = '\0';
+ get_sha1_hex(hex, sha1);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ char *commit_id;
+ char *url;
+ int arg = 1;
+ struct active_request_slot *slot;
+ char *low_speed_limit;
+ char *low_speed_time;
+ char *wait_url;
+ int rc = 0;
+
+ while (arg < argc && argv[arg][0] == '-') {
+ if (argv[arg][1] == 't') {
+ get_tree = 1;
+ } else if (argv[arg][1] == 'c') {
+ get_history = 1;
+ } else if (argv[arg][1] == 'a') {
+ get_all = 1;
+ get_tree = 1;
+ get_history = 1;
+ } else if (argv[arg][1] == 'v') {
+ get_verbosely = 1;
+ } else if (argv[arg][1] == 'w') {
+ write_ref = argv[arg + 1];
+ arg++;
+ } else if (!strcmp(argv[arg], "--recover")) {
+ get_recover = 1;
+ }
+ arg++;
+ }
+ if (argc < arg + 2) {
+ usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
+ return 1;
+ }
+ commit_id = argv[arg];
+ url = argv[arg + 1];
+
+ curl_global_init(CURL_GLOBAL_ALL);
+
+#ifdef USE_CURL_MULTI
+ {
+ char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
+ if (http_max_requests != NULL)
+ max_requests = atoi(http_max_requests);
+ }
+
+ curlm = curl_multi_init();
+ if (curlm == NULL) {
+ fprintf(stderr, "Error creating curl multi handle.\n");
+ return 1;
+ }
+#endif
+
+ if (getenv("GIT_SSL_NO_VERIFY"))
+ curl_ssl_verify = 0;
+
+ ssl_cert = getenv("GIT_SSL_CERT");
+#if LIBCURL_VERSION_NUM >= 0x070902
+ ssl_key = getenv("GIT_SSL_KEY");
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+ ssl_capath = getenv("GIT_SSL_CAPATH");
+#endif
+ ssl_cainfo = getenv("GIT_SSL_CAINFO");
+
+ low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
+ if (low_speed_limit != NULL)
+ curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
+ low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
+ if (low_speed_time != NULL)
+ curl_low_speed_time = strtol(low_speed_time, NULL, 10);
+
+ git_config(http_options);
+
+ if (curl_ssl_verify == -1)
+ curl_ssl_verify = 1;
+
+#ifdef USE_CURL_MULTI
+ if (max_requests < 1)
+ max_requests = DEFAULT_MAX_REQUESTS;
+#endif
+
+ pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache");
+ no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+ no_range_header = curl_slist_append(no_range_header, "Range:");
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+ curl_default = get_curl_handle();
+#endif
+
+ alt = xmalloc(sizeof(*alt));
+ alt->base = url;
+ alt->got_indices = 0;
+ alt->packs = NULL;
+ alt->next = NULL;
+
+ if (pull(commit_id))
+ rc = 1;
+
+ curl_slist_free_all(pragma_header);
+ curl_slist_free_all(no_pragma_header);
+ curl_slist_free_all(no_range_header);
+#ifndef NO_CURL_EASY_DUPHANDLE
+ curl_easy_cleanup(curl_default);
+#endif
+ slot = active_queue_head;
+ while (slot != NULL) {
+ if (slot->in_use) {
+ if (get_verbosely) {
+ curl_easy_getinfo(slot->curl,
+ CURLINFO_EFFECTIVE_URL,
+ &wait_url);
+ fprintf(stderr, "Waiting for %s\n", wait_url);
+ }
+ run_active_slot(slot);
+ }
+ if (slot->curl != NULL)
+ curl_easy_cleanup(slot->curl);
+ slot = slot->next;
+ }
+#ifdef USE_CURL_MULTI
+ curl_multi_cleanup(curlm);
+#endif
+ curl_global_cleanup();
+ return rc;
+}
diff --git a/http-push.c b/http-push.c
new file mode 100644
index 000000000..886618933
--- /dev/null
+++ b/http-push.c
@@ -0,0 +1,1829 @@
+#include "cache.h"
+#include "commit.h"
+#include "pack.h"
+#include "fetch.h"
+#include "tag.h"
+#include "blob.h"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+#include <expat.h>
+
+static const char http_push_usage[] =
+"git-http-push [--complete] [--force] [--verbose] <url> <ref> [<ref>...]\n";
+
+#if LIBCURL_VERSION_NUM >= 0x070908
+#define USE_CURL_MULTI
+#define DEFAULT_MAX_REQUESTS 5
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070704
+#define curl_global_cleanup() do { /* nothing */ } while(0)
+#endif
+#if LIBCURL_VERSION_NUM < 0x070800
+#define curl_global_init(a) do { /* nothing */ } while(0)
+#endif
+
+#if LIBCURL_VERSION_NUM < 0x070c04
+#define NO_CURL_EASY_DUPHANDLE
+#endif
+
+#ifndef XML_STATUS_OK
+enum XML_Status {
+ XML_STATUS_OK = 1,
+ XML_STATUS_ERROR = 0
+};
+#define XML_STATUS_OK 1
+#define XML_STATUS_ERROR 0
+#endif
+
+#define RANGE_HEADER_SIZE 30
+
+/* DAV method names and request body templates */
+#define DAV_LOCK "LOCK"
+#define DAV_MKCOL "MKCOL"
+#define DAV_MOVE "MOVE"
+#define DAV_PROPFIND "PROPFIND"
+#define DAV_PUT "PUT"
+#define DAV_UNLOCK "UNLOCK"
+#define PROPFIND_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:prop xmlns:R=\"%s\">\n<D:supportedlock/>\n</D:prop>\n</D:propfind>"
+#define LOCK_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:lockinfo xmlns:D=\"DAV:\">\n<D:lockscope><D:exclusive/></D:lockscope>\n<D:locktype><D:write/></D:locktype>\n<D:owner>\n<D:href>mailto:%s</D:href>\n</D:owner>\n</D:lockinfo>"
+
+#define LOCK_TIME 600
+#define LOCK_REFRESH 30
+
+static int active_requests = 0;
+static int data_received;
+static int pushing = 0;
+static int aborted = 0;
+static char remote_dir_exists[256];
+
+#ifdef USE_CURL_MULTI
+static int max_requests = -1;
+static CURLM *curlm;
+#endif
+#ifndef NO_CURL_EASY_DUPHANDLE
+static CURL *curl_default;
+#endif
+static struct curl_slist *no_pragma_header;
+static struct curl_slist *default_headers;
+static char curl_errorstr[CURL_ERROR_SIZE];
+
+static int push_verbosely = 0;
+static int push_all = 0;
+static int force_all = 0;
+
+struct buffer
+{
+ size_t posn;
+ size_t size;
+ void *buffer;
+};
+
+struct repo
+{
+ char *url;
+ struct packed_git *packs;
+};
+
+static struct repo *remote = NULL;
+
+enum transfer_state {
+ NEED_CHECK,
+ RUN_HEAD,
+ NEED_PUSH,
+ RUN_MKCOL,
+ RUN_PUT,
+ RUN_MOVE,
+ ABORTED,
+ COMPLETE,
+};
+
+struct transfer_request
+{
+ unsigned char sha1[20];
+ char *url;
+ char *dest;
+ struct active_lock *lock;
+ struct curl_slist *headers;
+ struct buffer buffer;
+ char filename[PATH_MAX];
+ char tmpfile[PATH_MAX];
+ enum transfer_state state;
+ CURLcode curl_result;
+ char errorstr[CURL_ERROR_SIZE];
+ long http_code;
+ unsigned char real_sha1[20];
+ SHA_CTX c;
+ z_stream stream;
+ int zret;
+ int rename;
+ struct active_request_slot *slot;
+ struct transfer_request *next;
+};
+
+struct active_request_slot
+{
+ CURL *curl;
+ FILE *local;
+ int in_use;
+ int done;
+ CURLcode curl_result;
+ long http_code;
+ struct active_request_slot *next;
+};
+
+static struct transfer_request *request_queue_head = NULL;
+static struct active_request_slot *active_queue_head = NULL;
+
+static int curl_ssl_verify = -1;
+static char *ssl_cert = NULL;
+#if LIBCURL_VERSION_NUM >= 0x070902
+static char *ssl_key = NULL;
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+static char *ssl_capath = NULL;
+#endif
+static char *ssl_cainfo = NULL;
+static long curl_low_speed_limit = -1;
+static long curl_low_speed_time = -1;
+
+struct active_lock
+{
+ int ctx_activelock;
+ int ctx_owner;
+ int ctx_owner_href;
+ int ctx_timeout;
+ int ctx_locktoken;
+ int ctx_locktoken_href;
+ char *url;
+ char *owner;
+ char *token;
+ time_t start_time;
+ long timeout;
+ int refreshing;
+};
+
+struct lockprop
+{
+ int supported_lock;
+ int lock_entry;
+ int lock_scope;
+ int lock_type;
+ int lock_exclusive;
+ int lock_exclusive_write;
+};
+
+static int http_options(const char *var, const char *value)
+{
+ if (!strcmp("http.sslverify", var)) {
+ if (curl_ssl_verify == -1) {
+ curl_ssl_verify = git_config_bool(var, value);
+ }
+ return 0;
+ }
+
+ if (!strcmp("http.sslcert", var)) {
+ if (ssl_cert == NULL) {
+ ssl_cert = xmalloc(strlen(value)+1);
+ strcpy(ssl_cert, value);
+ }
+ return 0;
+ }
+#if LIBCURL_VERSION_NUM >= 0x070902
+ if (!strcmp("http.sslkey", var)) {
+ if (ssl_key == NULL) {
+ ssl_key = xmalloc(strlen(value)+1);
+ strcpy(ssl_key, value);
+ }
+ return 0;
+ }
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+ if (!strcmp("http.sslcapath", var)) {
+ if (ssl_capath == NULL) {
+ ssl_capath = xmalloc(strlen(value)+1);
+ strcpy(ssl_capath, value);
+ }
+ return 0;
+ }
+#endif
+ if (!strcmp("http.sslcainfo", var)) {
+ if (ssl_cainfo == NULL) {
+ ssl_cainfo = xmalloc(strlen(value)+1);
+ strcpy(ssl_cainfo, value);
+ }
+ return 0;
+ }
+
+#ifdef USE_CURL_MULTI
+ if (!strcmp("http.maxrequests", var)) {
+ if (max_requests == -1)
+ max_requests = git_config_int(var, value);
+ return 0;
+ }
+#endif
+
+ if (!strcmp("http.lowspeedlimit", var)) {
+ if (curl_low_speed_limit == -1)
+ curl_low_speed_limit = (long)git_config_int(var, value);
+ return 0;
+ }
+ if (!strcmp("http.lowspeedtime", var)) {
+ if (curl_low_speed_time == -1)
+ curl_low_speed_time = (long)git_config_int(var, value);
+ return 0;
+ }
+
+ /* Fall back on the default ones */
+ return git_default_config(var, value);
+}
+
+static size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb,
+ struct buffer *buffer)
+{
+ size_t size = eltsize * nmemb;
+ if (size > buffer->size - buffer->posn)
+ size = buffer->size - buffer->posn;
+ memcpy(ptr, buffer->buffer + buffer->posn, size);
+ buffer->posn += size;
+ return size;
+}
+
+static size_t fwrite_buffer_dynamic(const void *ptr, size_t eltsize,
+ size_t nmemb, struct buffer *buffer)
+{
+ size_t size = eltsize * nmemb;
+ if (size > buffer->size - buffer->posn) {
+ buffer->size = buffer->size * 3 / 2;
+ if (buffer->size < buffer->posn + size)
+ buffer->size = buffer->posn + size;
+ buffer->buffer = xrealloc(buffer->buffer, buffer->size);
+ }
+ memcpy(buffer->buffer + buffer->posn, ptr, size);
+ buffer->posn += size;
+ data_received++;
+ return size;
+}
+
+static size_t fwrite_null(const void *ptr, size_t eltsize,
+ size_t nmemb, struct buffer *buffer)
+{
+ data_received++;
+ return eltsize * nmemb;
+}
+
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void);
+static void process_request_queue(void);
+#endif
+
+static CURL* get_curl_handle(void)
+{
+ CURL* result = curl_easy_init();
+
+ curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
+#if LIBCURL_VERSION_NUM >= 0x070907
+ curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
+#endif
+
+ if (ssl_cert != NULL)
+ curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
+#if LIBCURL_VERSION_NUM >= 0x070902
+ if (ssl_key != NULL)
+ curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+ if (ssl_capath != NULL)
+ curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath);
+#endif
+ if (ssl_cainfo != NULL)
+ curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo);
+ curl_easy_setopt(result, CURLOPT_FAILONERROR, 1);
+
+ if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {
+ curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT,
+ curl_low_speed_limit);
+ curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME,
+ curl_low_speed_time);
+ }
+
+ return result;
+}
+
+static struct active_request_slot *get_active_slot(void)
+{
+ struct active_request_slot *slot = active_queue_head;
+ struct active_request_slot *newslot;
+
+#ifdef USE_CURL_MULTI
+ int num_transfers;
+
+ /* Wait for a slot to open up if the queue is full */
+ while (active_requests >= max_requests) {
+ curl_multi_perform(curlm, &num_transfers);
+ if (num_transfers < active_requests) {
+ process_curl_messages();
+ }
+ }
+#endif
+
+ while (slot != NULL && slot->in_use) {
+ slot = slot->next;
+ }
+ if (slot == NULL) {
+ newslot = xmalloc(sizeof(*newslot));
+ newslot->curl = NULL;
+ newslot->in_use = 0;
+ newslot->next = NULL;
+
+ slot = active_queue_head;
+ if (slot == NULL) {
+ active_queue_head = newslot;
+ } else {
+ while (slot->next != NULL) {
+ slot = slot->next;
+ }
+ slot->next = newslot;
+ }
+ slot = newslot;
+ }
+
+ if (slot->curl == NULL) {
+#ifdef NO_CURL_EASY_DUPHANDLE
+ slot->curl = get_curl_handle();
+#else
+ slot->curl = curl_easy_duphandle(curl_default);
+#endif
+ }
+
+ active_requests++;
+ slot->in_use = 1;
+ slot->done = 0;
+ slot->local = NULL;
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, default_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
+
+ return slot;
+}
+
+static int start_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+ CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl);
+
+ if (curlm_result != CURLM_OK &&
+ curlm_result != CURLM_CALL_MULTI_PERFORM) {
+ active_requests--;
+ slot->in_use = 0;
+ return 0;
+ }
+#endif
+ return 1;
+}
+
+static void run_active_slot(struct active_request_slot *slot)
+{
+#ifdef USE_CURL_MULTI
+ int num_transfers;
+ long last_pos = 0;
+ long current_pos;
+ fd_set readfds;
+ fd_set writefds;
+ fd_set excfds;
+ int max_fd;
+ struct timeval select_timeout;
+ CURLMcode curlm_result;
+
+ while (!slot->done) {
+ data_received = 0;
+ do {
+ curlm_result = curl_multi_perform(curlm,
+ &num_transfers);
+ } while (curlm_result == CURLM_CALL_MULTI_PERFORM);
+ if (num_transfers < active_requests) {
+ process_curl_messages();
+ process_request_queue();
+ }
+
+ if (!data_received && slot->local != NULL) {
+ current_pos = ftell(slot->local);
+ if (current_pos > last_pos)
+ data_received++;
+ last_pos = current_pos;
+ }
+
+ if (!slot->done && !data_received) {
+ max_fd = 0;
+ FD_ZERO(&readfds);
+ FD_ZERO(&writefds);
+ FD_ZERO(&excfds);
+ select_timeout.tv_sec = 0;
+ select_timeout.tv_usec = 50000;
+ select(max_fd, &readfds, &writefds,
+ &excfds, &select_timeout);
+ }
+ }
+#else
+ slot->curl_result = curl_easy_perform(slot->curl);
+ active_requests--;
+#endif
+}
+
+static void start_check(struct transfer_request *request)
+{
+ char *hex = sha1_to_hex(request->sha1);
+ struct active_request_slot *slot;
+ char *posn;
+
+ request->url = xmalloc(strlen(remote->url) + 55);
+ strcpy(request->url, remote->url);
+ posn = request->url + strlen(remote->url);
+ strcpy(posn, "objects/");
+ posn += 8;
+ memcpy(posn, hex, 2);
+ posn += 2;
+ *(posn++) = '/';
+ strcpy(posn, hex + 2);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+
+ if (start_active_slot(slot)) {
+ request->slot = slot;
+ request->state = RUN_HEAD;
+ } else {
+ request->state = ABORTED;
+ free(request->url);
+ }
+}
+
+static void start_mkcol(struct transfer_request *request)
+{
+ char *hex = sha1_to_hex(request->sha1);
+ struct active_request_slot *slot;
+ char *posn;
+
+ request->url = xmalloc(strlen(remote->url) + 13);
+ strcpy(request->url, remote->url);
+ posn = request->url + strlen(remote->url);
+ strcpy(posn, "objects/");
+ posn += 8;
+ memcpy(posn, hex, 2);
+ posn += 2;
+ strcpy(posn, "/");
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
+ curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+ curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+
+ if (start_active_slot(slot)) {
+ request->slot = slot;
+ request->state = RUN_MKCOL;
+ } else {
+ request->state = ABORTED;
+ free(request->url);
+ }
+}
+
+static void start_put(struct transfer_request *request)
+{
+ char *hex = sha1_to_hex(request->sha1);
+ struct active_request_slot *slot;
+ char *posn;
+ char type[20];
+ char hdr[50];
+ void *unpacked;
+ unsigned long len;
+ int hdrlen;
+ ssize_t size;
+ z_stream stream;
+
+ unpacked = read_sha1_file(request->sha1, type, &len);
+ hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
+
+ /* Set it up */
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, Z_BEST_COMPRESSION);
+ size = deflateBound(&stream, len + hdrlen);
+ request->buffer.buffer = xmalloc(size);
+
+ /* Compress it */
+ stream.next_out = request->buffer.buffer;
+ stream.avail_out = size;
+
+ /* First header.. */
+ stream.next_in = (void *)hdr;
+ stream.avail_in = hdrlen;
+ while (deflate(&stream, 0) == Z_OK)
+ /* nothing */;
+
+ /* Then the data itself.. */
+ stream.next_in = unpacked;
+ stream.avail_in = len;
+ while (deflate(&stream, Z_FINISH) == Z_OK)
+ /* nothing */;
+ deflateEnd(&stream);
+ free(unpacked);
+
+ request->buffer.size = stream.total_out;
+ request->buffer.posn = 0;
+
+ if (request->url != NULL)
+ free(request->url);
+ request->url = xmalloc(strlen(remote->url) +
+ strlen(request->lock->token) + 51);
+ strcpy(request->url, remote->url);
+ posn = request->url + strlen(remote->url);
+ strcpy(posn, "objects/");
+ posn += 8;
+ memcpy(posn, hex, 2);
+ posn += 2;
+ *(posn++) = '/';
+ strcpy(posn, hex + 2);
+ request->dest = xmalloc(strlen(request->url) + 14);
+ sprintf(request->dest, "Destination: %s", request->url);
+ posn += 38;
+ *(posn++) = '.';
+ strcpy(posn, request->lock->token);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.size);
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+ curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+
+ if (start_active_slot(slot)) {
+ request->slot = slot;
+ request->state = RUN_PUT;
+ } else {
+ request->state = ABORTED;
+ free(request->url);
+ }
+}
+
+static void start_move(struct transfer_request *request)
+{
+ struct active_request_slot *slot;
+ struct curl_slist *dav_headers = NULL;
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); /* undo PUT setup */
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MOVE);
+ dav_headers = curl_slist_append(dav_headers, request->dest);
+ dav_headers = curl_slist_append(dav_headers, "Overwrite: T");
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, request->url);
+
+ if (start_active_slot(slot)) {
+ request->slot = slot;
+ request->state = RUN_MOVE;
+ } else {
+ request->state = ABORTED;
+ free(request->url);
+ }
+}
+
+static int refresh_lock(struct active_lock *lock)
+{
+ struct active_request_slot *slot;
+ char *if_header;
+ char timeout_header[25];
+ struct curl_slist *dav_headers = NULL;
+ int rc = 0;
+
+ lock->refreshing = 1;
+
+ if_header = xmalloc(strlen(lock->token) + 25);
+ sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+ sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout);
+ dav_headers = curl_slist_append(dav_headers, if_header);
+ dav_headers = curl_slist_append(dav_headers, timeout_header);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK) {
+ fprintf(stderr, "Got HTTP error %ld\n", slot->http_code);
+ } else {
+ lock->start_time = time(NULL);
+ rc = 1;
+ }
+ }
+
+ lock->refreshing = 0;
+ curl_slist_free_all(dav_headers);
+ free(if_header);
+
+ return rc;
+}
+
+static void finish_request(struct transfer_request *request)
+{
+ time_t current_time = time(NULL);
+ int time_remaining;
+
+ request->curl_result = request->slot->curl_result;
+ request->http_code = request->slot->http_code;
+ request->slot = NULL;
+
+ /* Refresh the lock if it is close to timing out */
+ time_remaining = request->lock->start_time + request->lock->timeout
+ - current_time;
+ if (time_remaining < LOCK_REFRESH && !request->lock->refreshing) {
+ if (!refresh_lock(request->lock)) {
+ fprintf(stderr, "Unable to refresh remote lock\n");
+ aborted = 1;
+ }
+ }
+
+ if (request->headers != NULL)
+ curl_slist_free_all(request->headers);
+ if (request->state == RUN_HEAD) {
+ if (request->http_code == 404) {
+ request->state = NEED_PUSH;
+ } else if (request->curl_result == CURLE_OK) {
+ remote_dir_exists[request->sha1[0]] = 1;
+ request->state = COMPLETE;
+ } else {
+ fprintf(stderr, "HEAD %s failed, aborting (%d/%ld)\n",
+ sha1_to_hex(request->sha1),
+ request->curl_result, request->http_code);
+ request->state = ABORTED;
+ aborted = 1;
+ }
+ } else if (request->state == RUN_MKCOL) {
+ if (request->curl_result == CURLE_OK ||
+ request->http_code == 405) {
+ remote_dir_exists[request->sha1[0]] = 1;
+ start_put(request);
+ } else {
+ fprintf(stderr, "MKCOL %s failed, aborting (%d/%ld)\n",
+ sha1_to_hex(request->sha1),
+ request->curl_result, request->http_code);
+ request->state = ABORTED;
+ aborted = 1;
+ }
+ } else if (request->state == RUN_PUT) {
+ if (request->curl_result == CURLE_OK) {
+ start_move(request);
+ } else {
+ fprintf(stderr, "PUT %s failed, aborting (%d/%ld)\n",
+ sha1_to_hex(request->sha1),
+ request->curl_result, request->http_code);
+ request->state = ABORTED;
+ aborted = 1;
+ }
+ } else if (request->state == RUN_MOVE) {
+ if (request->curl_result == CURLE_OK) {
+ if (push_verbosely)
+ fprintf(stderr,
+ "sent %s\n",
+ sha1_to_hex(request->sha1));
+ request->state = COMPLETE;
+ } else {
+ fprintf(stderr, "MOVE %s failed, aborting (%d/%ld)\n",
+ sha1_to_hex(request->sha1),
+ request->curl_result, request->http_code);
+ request->state = ABORTED;
+ aborted = 1;
+ }
+ }
+}
+
+static void release_request(struct transfer_request *request)
+{
+ struct transfer_request *entry = request_queue_head;
+
+ if (request == request_queue_head) {
+ request_queue_head = request->next;
+ } else {
+ while (entry->next != NULL && entry->next != request)
+ entry = entry->next;
+ if (entry->next == request)
+ entry->next = entry->next->next;
+ }
+
+ free(request->url);
+ free(request);
+}
+
+#ifdef USE_CURL_MULTI
+static void process_curl_messages(void)
+{
+ int num_messages;
+ struct active_request_slot *slot;
+ struct transfer_request *request = NULL;
+ CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages);
+
+ while (curl_message != NULL) {
+ if (curl_message->msg == CURLMSG_DONE) {
+ slot = active_queue_head;
+ while (slot != NULL &&
+ slot->curl != curl_message->easy_handle)
+ slot = slot->next;
+ if (slot != NULL) {
+ int curl_result = curl_message->data.result;
+ curl_multi_remove_handle(curlm, slot->curl);
+ active_requests--;
+ slot->done = 1;
+ slot->in_use = 0;
+ slot->curl_result = curl_result;
+ curl_easy_getinfo(slot->curl,
+ CURLINFO_HTTP_CODE,
+ &slot->http_code);
+ request = request_queue_head;
+ while (request != NULL &&
+ request->slot != slot)
+ request = request->next;
+ if (request != NULL)
+ finish_request(request);
+ } else {
+ fprintf(stderr, "Received DONE message for unknown request!\n");
+ }
+ } else {
+ fprintf(stderr, "Unknown CURL message received: %d\n",
+ (int)curl_message->msg);
+ }
+ curl_message = curl_multi_info_read(curlm, &num_messages);
+ }
+}
+
+static void process_request_queue(void)
+{
+ struct transfer_request *request = request_queue_head;
+ struct active_request_slot *slot = active_queue_head;
+ int num_transfers;
+
+ if (aborted)
+ return;
+
+ while (active_requests < max_requests && request != NULL) {
+ if (!pushing && request->state == NEED_CHECK) {
+ start_check(request);
+ curl_multi_perform(curlm, &num_transfers);
+ } else if (pushing && request->state == NEED_PUSH) {
+ if (remote_dir_exists[request->sha1[0]])
+ start_put(request);
+ else
+ start_mkcol(request);
+ curl_multi_perform(curlm, &num_transfers);
+ }
+ request = request->next;
+ }
+
+ while (slot != NULL) {
+ if (!slot->in_use && slot->curl != NULL) {
+ curl_easy_cleanup(slot->curl);
+ slot->curl = NULL;
+ }
+ slot = slot->next;
+ }
+}
+#endif
+
+static void process_waiting_requests(void)
+{
+ struct active_request_slot *slot = active_queue_head;
+
+ while (slot != NULL)
+ if (slot->in_use) {
+ run_active_slot(slot);
+ slot = active_queue_head;
+ } else {
+ slot = slot->next;
+ }
+}
+
+static void add_request(unsigned char *sha1, struct active_lock *lock)
+{
+ struct transfer_request *request = request_queue_head;
+ struct packed_git *target;
+
+ while (request != NULL && memcmp(request->sha1, sha1, 20))
+ request = request->next;
+ if (request != NULL)
+ return;
+
+ target = find_sha1_pack(sha1, remote->packs);
+ if (target)
+ return;
+
+ request = xmalloc(sizeof(*request));
+ memcpy(request->sha1, sha1, 20);
+ request->url = NULL;
+ request->lock = lock;
+ request->headers = NULL;
+ request->state = NEED_CHECK;
+ request->next = request_queue_head;
+ request_queue_head = request;
+#ifdef USE_CURL_MULTI
+ process_request_queue();
+ process_curl_messages();
+#endif
+}
+
+static int fetch_index(unsigned char *sha1)
+{
+ char *hex = sha1_to_hex(sha1);
+ char *filename;
+ char *url;
+ char tmpfile[PATH_MAX];
+ long prev_posn = 0;
+ char range[RANGE_HEADER_SIZE];
+ struct curl_slist *range_header = NULL;
+
+ FILE *indexfile;
+ struct active_request_slot *slot;
+
+ /* Don't use the index if the pack isn't there */
+ url = xmalloc(strlen(remote->url) + 65);
+ sprintf(url, "%s/objects/pack/pack-%s.pack", remote->url, hex);
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK) {
+ free(url);
+ return error("Unable to verify pack %s is available",
+ hex);
+ }
+ } else {
+ return error("Unable to start request");
+ }
+
+ if (has_pack_index(sha1))
+ return 0;
+
+ if (push_verbosely)
+ fprintf(stderr, "Getting index for pack %s\n", hex);
+
+ sprintf(url, "%s/objects/pack/pack-%s.idx", remote->url, hex);
+
+ filename = sha1_pack_index_name(sha1);
+ snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
+ indexfile = fopen(tmpfile, "a");
+ if (!indexfile)
+ return error("Unable to open local file %s for pack index",
+ filename);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header);
+ slot->local = indexfile;
+
+ /* If there is data present from a previous transfer attempt,
+ resume where it left off */
+ prev_posn = ftell(indexfile);
+ if (prev_posn>0) {
+ if (push_verbosely)
+ fprintf(stderr,
+ "Resuming fetch of index for pack %s at byte %ld\n",
+ hex, prev_posn);
+ sprintf(range, "Range: bytes=%ld-", prev_posn);
+ range_header = curl_slist_append(range_header, range);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
+ }
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK) {
+ free(url);
+ fclose(indexfile);
+ return error("Unable to get pack index %s\n%s", url,
+ curl_errorstr);
+ }
+ } else {
+ free(url);
+ return error("Unable to start request");
+ }
+
+ free(url);
+ fclose(indexfile);
+
+ return move_temp_to_file(tmpfile, filename);
+}
+
+static int setup_index(unsigned char *sha1)
+{
+ struct packed_git *new_pack;
+
+ if (fetch_index(sha1))
+ return -1;
+
+ new_pack = parse_pack_index(sha1);
+ new_pack->next = remote->packs;
+ remote->packs = new_pack;
+ return 0;
+}
+
+static int fetch_indices(void)
+{
+ unsigned char sha1[20];
+ char *url;
+ struct buffer buffer;
+ char *data;
+ int i = 0;
+
+ struct active_request_slot *slot;
+
+ data = xmalloc(4096);
+ memset(data, 0, 4096);
+ buffer.size = 4096;
+ buffer.posn = 0;
+ buffer.buffer = data;
+
+ if (push_verbosely)
+ fprintf(stderr, "Getting pack list\n");
+
+ url = xmalloc(strlen(remote->url) + 21);
+ sprintf(url, "%s/objects/info/packs", remote->url);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+ fwrite_buffer_dynamic);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK) {
+ free(buffer.buffer);
+ free(url);
+ if (slot->http_code == 404)
+ return 0;
+ else
+ return error("%s", curl_errorstr);
+ }
+ } else {
+ free(buffer.buffer);
+ free(url);
+ return error("Unable to start request");
+ }
+ free(url);
+
+ data = buffer.buffer;
+ while (i < buffer.posn) {
+ switch (data[i]) {
+ case 'P':
+ i++;
+ if (i + 52 < buffer.posn &&
+ !strncmp(data + i, " pack-", 6) &&
+ !strncmp(data + i + 46, ".pack\n", 6)) {
+ get_sha1_hex(data + i + 6, sha1);
+ setup_index(sha1);
+ i += 51;
+ break;
+ }
+ default:
+ while (data[i] != '\n')
+ i++;
+ }
+ i++;
+ }
+
+ free(buffer.buffer);
+ return 0;
+}
+
+static inline int needs_quote(int ch)
+{
+ switch (ch) {
+ case '/': case '-': case '.':
+ case 'A'...'Z': case 'a'...'z': case '0'...'9':
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+static inline int hex(int v)
+{
+ if (v < 10) return '0' + v;
+ else return 'A' + v - 10;
+}
+
+static char *quote_ref_url(const char *base, const char *ref)
+{
+ const char *cp;
+ char *dp, *qref;
+ int len, baselen, ch;
+
+ baselen = strlen(base);
+ len = baselen + 12; /* "refs/heads/" + NUL */
+ for (cp = ref; (ch = *cp) != 0; cp++, len++)
+ if (needs_quote(ch))
+ len += 2; /* extra two hex plus replacement % */
+ qref = xmalloc(len);
+ memcpy(qref, base, baselen);
+ memcpy(qref + baselen, "refs/heads/", 11);
+ for (cp = ref, dp = qref + baselen + 11; (ch = *cp) != 0; cp++) {
+ if (needs_quote(ch)) {
+ *dp++ = '%';
+ *dp++ = hex((ch >> 4) & 0xF);
+ *dp++ = hex(ch & 0xF);
+ }
+ else
+ *dp++ = ch;
+ }
+ *dp = 0;
+
+ return qref;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+ char *url;
+ char hex[42];
+ struct buffer buffer;
+ char *base = remote->url;
+ struct active_request_slot *slot;
+ buffer.size = 41;
+ buffer.posn = 0;
+ buffer.buffer = hex;
+ hex[41] = '\0';
+
+ url = quote_ref_url(base, ref);
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+ fwrite_buffer_dynamic);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK)
+ return error("Couldn't get %s for %s\n%s",
+ url, ref, curl_errorstr);
+ } else {
+ return error("Unable to start request");
+ }
+
+ hex[40] = '\0';
+ get_sha1_hex(hex, sha1);
+ return 0;
+}
+
+static void
+start_activelock_element(void *userData, const char *name, const char **atts)
+{
+ struct active_lock *lock = (struct active_lock *)userData;
+
+ if (lock->ctx_activelock && !strcmp(name, "D:timeout"))
+ lock->ctx_timeout = 1;
+ else if (lock->ctx_owner && strstr(name, "href"))
+ lock->ctx_owner_href = 1;
+ else if (lock->ctx_activelock && strstr(name, "owner"))
+ lock->ctx_owner = 1;
+ else if (lock->ctx_locktoken && !strcmp(name, "D:href"))
+ lock->ctx_locktoken_href = 1;
+ else if (lock->ctx_activelock && !strcmp(name, "D:locktoken"))
+ lock->ctx_locktoken = 1;
+ else if (!strcmp(name, "D:activelock"))
+ lock->ctx_activelock = 1;
+}
+
+static void
+end_activelock_element(void *userData, const char *name)
+{
+ struct active_lock *lock = (struct active_lock *)userData;
+
+ if (lock->ctx_timeout && !strcmp(name, "D:timeout")) {
+ lock->ctx_timeout = 0;
+ } else if (lock->ctx_owner_href && strstr(name, "href")) {
+ lock->ctx_owner_href = 0;
+ } else if (lock->ctx_owner && strstr(name, "owner")) {
+ lock->ctx_owner = 0;
+ } else if (lock->ctx_locktoken_href && !strcmp(name, "D:href")) {
+ lock->ctx_locktoken_href = 0;
+ } else if (lock->ctx_locktoken && !strcmp(name, "D:locktoken")) {
+ lock->ctx_locktoken = 0;
+ } else if (lock->ctx_activelock && !strcmp(name, "D:activelock")) {
+ lock->ctx_activelock = 0;
+ }
+}
+
+static void
+activelock_cdata(void *userData, const XML_Char *s, int len)
+{
+ struct active_lock *lock = (struct active_lock *)userData;
+ char *this = malloc(len+1);
+ strncpy(this, s, len);
+
+ if (lock->ctx_owner_href) {
+ lock->owner = malloc(len+1);
+ strcpy(lock->owner, this);
+ } else if (lock->ctx_locktoken_href) {
+ if (!strncmp(this, "opaquelocktoken:", 16)) {
+ lock->token = malloc(len-15);
+ strcpy(lock->token, this+16);
+ }
+ } else if (lock->ctx_timeout) {
+ if (!strncmp(this, "Second-", 7))
+ lock->timeout = strtol(this+7, NULL, 10);
+ }
+
+ free(this);
+}
+
+static void
+start_lockprop_element(void *userData, const char *name, const char **atts)
+{
+ struct lockprop *prop = (struct lockprop *)userData;
+
+ if (prop->lock_type && !strcmp(name, "D:write")) {
+ if (prop->lock_exclusive) {
+ prop->lock_exclusive_write = 1;
+ }
+ } else if (prop->lock_scope && !strcmp(name, "D:exclusive")) {
+ prop->lock_exclusive = 1;
+ } else if (prop->lock_entry) {
+ if (!strcmp(name, "D:lockscope")) {
+ prop->lock_scope = 1;
+ } else if (!strcmp(name, "D:locktype")) {
+ prop->lock_type = 1;
+ }
+ } else if (prop->supported_lock) {
+ if (!strcmp(name, "D:lockentry")) {
+ prop->lock_entry = 1;
+ }
+ } else if (!strcmp(name, "D:supportedlock")) {
+ prop->supported_lock = 1;
+ }
+}
+
+static void
+end_lockprop_element(void *userData, const char *name)
+{
+ struct lockprop *prop = (struct lockprop *)userData;
+
+ if (!strcmp(name, "D:lockentry")) {
+ prop->lock_entry = 0;
+ prop->lock_scope = 0;
+ prop->lock_type = 0;
+ prop->lock_exclusive = 0;
+ } else if (!strcmp(name, "D:supportedlock")) {
+ prop->supported_lock = 0;
+ }
+}
+
+static struct active_lock *lock_remote(char *file, long timeout)
+{
+ struct active_request_slot *slot;
+ struct buffer out_buffer;
+ struct buffer in_buffer;
+ char *out_data;
+ char *in_data;
+ char *url;
+ char *ep;
+ char timeout_header[25];
+ struct active_lock *new_lock;
+ XML_Parser parser = XML_ParserCreate(NULL);
+ enum XML_Status result;
+ struct curl_slist *dav_headers = NULL;
+
+ url = xmalloc(strlen(remote->url) + strlen(file) + 1);
+ sprintf(url, "%s%s", remote->url, file);
+
+ /* Make sure leading directories exist for the remote ref */
+ ep = strchr(url + strlen(remote->url) + 11, '/');
+ while (ep) {
+ *ep = 0;
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK &&
+ slot->http_code != 405) {
+ fprintf(stderr,
+ "Unable to create branch path %s\n",
+ url);
+ free(url);
+ return NULL;
+ }
+ } else {
+ fprintf(stderr, "Unable to start request\n");
+ free(url);
+ return NULL;
+ }
+ *ep = '/';
+ ep = strchr(ep + 1, '/');
+ }
+
+ out_buffer.size = strlen(LOCK_REQUEST) + strlen(git_default_email) - 2;
+ out_data = xmalloc(out_buffer.size + 1);
+ snprintf(out_data, out_buffer.size + 1, LOCK_REQUEST, git_default_email);
+ out_buffer.posn = 0;
+ out_buffer.buffer = out_data;
+
+ in_buffer.size = 4096;
+ in_data = xmalloc(in_buffer.size);
+ in_buffer.posn = 0;
+ in_buffer.buffer = in_data;
+
+ new_lock = xcalloc(1, sizeof(*new_lock));
+ new_lock->owner = NULL;
+ new_lock->token = NULL;
+ new_lock->timeout = -1;
+ new_lock->refreshing = 0;
+
+ sprintf(timeout_header, "Timeout: Second-%ld", timeout);
+ dav_headers = curl_slist_append(dav_headers, timeout_header);
+ dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+ fwrite_buffer_dynamic);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, url);
+ curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result != CURLE_OK) {
+ fprintf(stderr, "Got HTTP error %ld\n", slot->http_code);
+ free(new_lock);
+ free(url);
+ free(out_data);
+ free(in_data);
+ return NULL;
+ }
+ } else {
+ free(new_lock);
+ free(url);
+ free(out_data);
+ free(in_data);
+ fprintf(stderr, "Unable to start request\n");
+ return NULL;
+ }
+
+ free(out_data);
+
+ XML_SetUserData(parser, new_lock);
+ XML_SetElementHandler(parser, start_activelock_element,
+ end_activelock_element);
+ XML_SetCharacterDataHandler(parser, activelock_cdata);
+ result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1);
+ free(in_data);
+ if (result != XML_STATUS_OK) {
+ fprintf(stderr, "%s", XML_ErrorString(
+ XML_GetErrorCode(parser)));
+ free(url);
+ free(new_lock);
+ return NULL;
+ }
+
+ if (new_lock->token == NULL || new_lock->timeout <= 0) {
+ if (new_lock->token != NULL)
+ free(new_lock->token);
+ if (new_lock->owner != NULL)
+ free(new_lock->owner);
+ free(url);
+ free(new_lock);
+ return NULL;
+ }
+
+ new_lock->url = url;
+ new_lock->start_time = time(NULL);
+ return new_lock;
+}
+
+static int unlock_remote(struct active_lock *lock)
+{
+ struct active_request_slot *slot;
+ char *lock_token_header;
+ struct curl_slist *dav_headers = NULL;
+ int rc = 0;
+
+ lock_token_header = xmalloc(strlen(lock->token) + 31);
+ sprintf(lock_token_header, "Lock-Token: <opaquelocktoken:%s>",
+ lock->token);
+ dav_headers = curl_slist_append(dav_headers, lock_token_header);
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ if (slot->curl_result == CURLE_OK)
+ rc = 1;
+ else
+ fprintf(stderr, "Got HTTP error %ld\n",
+ slot->http_code);
+ } else {
+ fprintf(stderr, "Unable to start request\n");
+ }
+
+ curl_slist_free_all(dav_headers);
+ free(lock_token_header);
+
+ if (lock->owner != NULL)
+ free(lock->owner);
+ free(lock->url);
+ free(lock->token);
+ free(lock);
+
+ return rc;
+}
+
+static int check_locking(void)
+{
+ struct active_request_slot *slot;
+ struct buffer in_buffer;
+ struct buffer out_buffer;
+ char *in_data;
+ char *out_data;
+ XML_Parser parser = XML_ParserCreate(NULL);
+ enum XML_Status result;
+ struct lockprop supported_lock;
+ struct curl_slist *dav_headers = NULL;
+
+ out_buffer.size = strlen(PROPFIND_REQUEST) + strlen(remote->url) - 2;
+ out_data = xmalloc(out_buffer.size + 1);
+ snprintf(out_data, out_buffer.size + 1, PROPFIND_REQUEST, remote->url);
+ out_buffer.posn = 0;
+ out_buffer.buffer = out_data;
+
+ in_buffer.size = 4096;
+ in_data = xmalloc(in_buffer.size);
+ in_buffer.posn = 0;
+ in_buffer.buffer = in_data;
+
+ dav_headers = curl_slist_append(dav_headers, "Depth: 0");
+ dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
+ fwrite_buffer_dynamic);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url);
+ curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ free(out_data);
+ if (slot->curl_result != CURLE_OK) {
+ free(in_buffer.buffer);
+ return -1;
+ }
+
+ XML_SetUserData(parser, &supported_lock);
+ XML_SetElementHandler(parser, start_lockprop_element,
+ end_lockprop_element);
+ result = XML_Parse(parser, in_buffer.buffer, in_buffer.posn, 1);
+ free(in_buffer.buffer);
+ if (result != XML_STATUS_OK)
+ return error("%s", XML_ErrorString(
+ XML_GetErrorCode(parser)));
+ } else {
+ free(out_data);
+ free(in_buffer.buffer);
+ return error("Unable to start request");
+ }
+
+ if (supported_lock.lock_exclusive_write)
+ return 0;
+ else
+ return 1;
+}
+
+static int is_ancestor(unsigned char *sha1, struct commit *commit)
+{
+ struct commit_list *parents;
+
+ if (parse_commit(commit))
+ return 0;
+ parents = commit->parents;
+ for (; parents; parents = parents->next) {
+ if (!memcmp(sha1, parents->item->object.sha1, 20)) {
+ return 1;
+ } else if (parents->item->object.type == commit_type) {
+ if (is_ancestor(
+ sha1,
+ (struct commit *)&parents->item->object
+ ))
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void get_delta(unsigned char *sha1, struct object *obj,
+ struct active_lock *lock)
+{
+ struct commit *commit;
+ struct commit_list *parents;
+ struct tree *tree;
+ struct tree_entry_list *entry;
+
+ if (sha1 && !memcmp(sha1, obj->sha1, 20))
+ return;
+
+ if (aborted)
+ return;
+
+ if (obj->type == commit_type) {
+ if (push_verbosely)
+ fprintf(stderr, "walk %s\n", sha1_to_hex(obj->sha1));
+ add_request(obj->sha1, lock);
+ commit = (struct commit *)obj;
+ if (parse_commit(commit)) {
+ fprintf(stderr, "Error parsing commit %s\n",
+ sha1_to_hex(obj->sha1));
+ aborted = 1;
+ return;
+ }
+ parents = commit->parents;
+ for (; parents; parents = parents->next)
+ if (sha1 == NULL ||
+ memcmp(sha1, parents->item->object.sha1, 20))
+ get_delta(sha1, &parents->item->object,
+ lock);
+ get_delta(sha1, &commit->tree->object, lock);
+ } else if (obj->type == tree_type) {
+ if (push_verbosely)
+ fprintf(stderr, "walk %s\n", sha1_to_hex(obj->sha1));
+ add_request(obj->sha1, lock);
+ tree = (struct tree *)obj;
+ if (parse_tree(tree)) {
+ fprintf(stderr, "Error parsing tree %s\n",
+ sha1_to_hex(obj->sha1));
+ aborted = 1;
+ return;
+ }
+ entry = tree->entries;
+ tree->entries = NULL;
+ while (entry) {
+ struct tree_entry_list *next = entry->next;
+ get_delta(sha1, entry->item.any, lock);
+ free(entry->name);
+ free(entry);
+ entry = next;
+ }
+ } else if (obj->type == blob_type || obj->type == tag_type) {
+ add_request(obj->sha1, lock);
+ }
+}
+
+static int update_remote(unsigned char *sha1, struct active_lock *lock)
+{
+ struct active_request_slot *slot;
+ char *out_data;
+ char *if_header;
+ struct buffer out_buffer;
+ struct curl_slist *dav_headers = NULL;
+ int i;
+
+ if_header = xmalloc(strlen(lock->token) + 25);
+ sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token);
+ dav_headers = curl_slist_append(dav_headers, if_header);
+
+ out_buffer.size = 41;
+ out_data = xmalloc(out_buffer.size + 1);
+ i = snprintf(out_data, out_buffer.size + 1, "%s\n", sha1_to_hex(sha1));
+ if (i != out_buffer.size) {
+ fprintf(stderr, "Unable to initialize PUT request body\n");
+ return 0;
+ }
+ out_buffer.posn = 0;
+ out_buffer.buffer = out_data;
+
+ slot = get_active_slot();
+ curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
+ curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
+ curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
+ curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
+ curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_PUT, 1);
+ curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url);
+
+ if (start_active_slot(slot)) {
+ run_active_slot(slot);
+ free(out_data);
+ free(if_header);
+ if (slot->curl_result != CURLE_OK) {
+ fprintf(stderr,
+ "PUT error: curl result=%d, HTTP code=%ld\n",
+ slot->curl_result, slot->http_code);
+ /* We should attempt recovery? */
+ return 0;
+ }
+ } else {
+ free(out_data);
+ free(if_header);
+ fprintf(stderr, "Unable to start PUT request\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ struct active_request_slot *slot;
+ struct active_request_slot *next_slot;
+ struct transfer_request *request;
+ struct transfer_request *next_request;
+ int nr_refspec = 0;
+ char **refspec = NULL;
+ int do_remote_update;
+ int new_branch;
+ int force_this;
+ char *local_ref;
+ unsigned char local_sha1[20];
+ struct object *local_object = NULL;
+ char *remote_ref = NULL;
+ unsigned char remote_sha1[20];
+ struct active_lock *remote_lock;
+ char *remote_path = NULL;
+ char *low_speed_limit;
+ char *low_speed_time;
+ int rc = 0;
+ int i;
+
+ setup_ident();
+
+ remote = xmalloc(sizeof(*remote));
+ remote->url = NULL;
+ remote->packs = NULL;
+
+ argv++;
+ for (i = 1; i < argc; i++, argv++) {
+ char *arg = *argv;
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "--complete")) {
+ push_all = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--force")) {
+ force_all = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--verbose")) {
+ push_verbosely = 1;
+ continue;
+ }
+ usage(http_push_usage);
+ }
+ if (!remote->url) {
+ remote->url = arg;
+ continue;
+ }
+ refspec = argv;
+ nr_refspec = argc - i;
+ break;
+ }
+
+ memset(remote_dir_exists, 0, 256);
+
+ curl_global_init(CURL_GLOBAL_ALL);
+
+#ifdef USE_CURL_MULTI
+ {
+ char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS");
+ if (http_max_requests != NULL)
+ max_requests = atoi(http_max_requests);
+ }
+
+ curlm = curl_multi_init();
+ if (curlm == NULL) {
+ fprintf(stderr, "Error creating curl multi handle.\n");
+ return 1;
+ }
+#endif
+
+ if (getenv("GIT_SSL_NO_VERIFY"))
+ curl_ssl_verify = 0;
+
+ ssl_cert = getenv("GIT_SSL_CERT");
+#if LIBCURL_VERSION_NUM >= 0x070902
+ ssl_key = getenv("GIT_SSL_KEY");
+#endif
+#if LIBCURL_VERSION_NUM >= 0x070908
+ ssl_capath = getenv("GIT_SSL_CAPATH");
+#endif
+ ssl_cainfo = getenv("GIT_SSL_CAINFO");
+
+ low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
+ if (low_speed_limit != NULL)
+ curl_low_speed_limit = strtol(low_speed_limit, NULL, 10);
+ low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME");
+ if (low_speed_time != NULL)
+ curl_low_speed_time = strtol(low_speed_time, NULL, 10);
+
+ git_config(http_options);
+
+ if (curl_ssl_verify == -1)
+ curl_ssl_verify = 1;
+
+#ifdef USE_CURL_MULTI
+ if (max_requests < 1)
+ max_requests = DEFAULT_MAX_REQUESTS;
+#endif
+
+ no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
+ default_headers = curl_slist_append(default_headers, "Range:");
+ default_headers = curl_slist_append(default_headers, "Destination:");
+ default_headers = curl_slist_append(default_headers, "If:");
+ default_headers = curl_slist_append(default_headers,
+ "Pragma: no-cache");
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+ curl_default = get_curl_handle();
+#endif
+
+ /* Verify DAV compliance/lock support */
+ if (check_locking() != 0) {
+ fprintf(stderr, "Error: no DAV locking support on remote repo %s\n", remote->url);
+ rc = 1;
+ goto cleanup;
+ }
+
+ /* Process each refspec */
+ for (i = 0; i < nr_refspec; i++) {
+ char *ep;
+ force_this = 0;
+ do_remote_update = 0;
+ new_branch = 0;
+ local_ref = refspec[i];
+ if (*local_ref == '+') {
+ force_this = 1;
+ local_ref++;
+ }
+ ep = strchr(local_ref, ':');
+ if (ep) {
+ remote_ref = ep + 1;
+ *ep = 0;
+ }
+ else
+ remote_ref = local_ref;
+
+ /* Lock remote branch ref */
+ if (remote_path)
+ free(remote_path);
+ remote_path = xmalloc(strlen(remote_ref) + 12);
+ sprintf(remote_path, "refs/heads/%s", remote_ref);
+ remote_lock = lock_remote(remote_path, LOCK_TIME);
+ if (remote_lock == NULL) {
+ fprintf(stderr, "Unable to lock remote branch %s\n",
+ remote_ref);
+ rc = 1;
+ continue;
+ }
+
+ /* Resolve local and remote refs */
+ if (fetch_ref(remote_ref, remote_sha1) != 0) {
+ fprintf(stderr,
+ "Remote branch %s does not exist on %s\n",
+ remote_ref, remote->url);
+ new_branch = 1;
+ }
+ if (get_sha1(local_ref, local_sha1) != 0) {
+ fprintf(stderr, "Error resolving local branch %s\n",
+ local_ref);
+ rc = 1;
+ goto unlock;
+ }
+
+ /* Find relationship between local and remote */
+ local_object = parse_object(local_sha1);
+ if (!local_object) {
+ fprintf(stderr, "Unable to parse local object %s\n",
+ sha1_to_hex(local_sha1));
+ rc = 1;
+ goto unlock;
+ } else if (new_branch) {
+ do_remote_update = 1;
+ } else {
+ if (!memcmp(local_sha1, remote_sha1, 20)) {
+ fprintf(stderr,
+ "* %s: same as branch '%s' of %s\n",
+ local_ref, remote_ref, remote->url);
+ } else if (is_ancestor(remote_sha1,
+ (struct commit *)local_object)) {
+ fprintf(stderr,
+ "Remote %s will fast-forward to local %s\n",
+ remote_ref, local_ref);
+ do_remote_update = 1;
+ } else if (force_all || force_this) {
+ fprintf(stderr,
+ "* %s on %s does not fast forward to local branch '%s', overwriting\n",
+ remote_ref, remote->url, local_ref);
+ do_remote_update = 1;
+ } else {
+ fprintf(stderr,
+ "* %s on %s does not fast forward to local branch '%s'\n",
+ remote_ref, remote->url, local_ref);
+ rc = 1;
+ goto unlock;
+ }
+ }
+
+ /* Generate and check list of required objects */
+ pushing = 0;
+ if (do_remote_update || push_all)
+ fetch_indices();
+ get_delta(push_all ? NULL : remote_sha1,
+ local_object, remote_lock);
+ process_waiting_requests();
+
+ /* Push missing objects to remote, this would be a
+ convenient time to pack them first if appropriate. */
+ pushing = 1;
+ process_request_queue();
+ process_waiting_requests();
+
+ /* Update the remote branch if all went well */
+ if (do_remote_update) {
+ if (!aborted && update_remote(local_sha1,
+ remote_lock)) {
+ fprintf(stderr, "%s remote branch %s\n",
+ new_branch ? "Created" : "Updated",
+ remote_ref);
+ } else {
+ fprintf(stderr,
+ "Unable to %s remote branch %s\n",
+ new_branch ? "create" : "update",
+ remote_ref);
+ rc = 1;
+ goto unlock;
+ }
+ }
+
+ unlock:
+ unlock_remote(remote_lock);
+ free(remote_path);
+ }
+
+ cleanup:
+ free(remote);
+
+ curl_slist_free_all(no_pragma_header);
+ curl_slist_free_all(default_headers);
+
+ slot = active_queue_head;
+ while (slot != NULL) {
+ next_slot = slot->next;
+ if (slot->curl != NULL)
+ curl_easy_cleanup(slot->curl);
+ free(slot);
+ slot = next_slot;
+ }
+
+ request = request_queue_head;
+ while (request != NULL) {
+ next_request = request->next;
+ release_request(request);
+ request = next_request;
+ }
+
+#ifndef NO_CURL_EASY_DUPHANDLE
+ curl_easy_cleanup(curl_default);
+#endif
+#ifdef USE_CURL_MULTI
+ curl_multi_cleanup(curlm);
+#endif
+ curl_global_cleanup();
+ return rc;
+}
diff --git a/ident.c b/ident.c
new file mode 100644
index 000000000..bc89e1d04
--- /dev/null
+++ b/ident.c
@@ -0,0 +1,192 @@
+/*
+ * ident.c
+ *
+ * create git identifier lines of the form "name <email> date"
+ *
+ * Copyright (C) 2005 Linus Torvalds
+ */
+#include "cache.h"
+
+#include <pwd.h>
+#include <netdb.h>
+
+static char git_default_date[50];
+
+static void copy_gecos(struct passwd *w, char *name, int sz)
+{
+ char *src, *dst;
+ int len, nlen;
+
+ nlen = strlen(w->pw_name);
+
+ /* Traditionally GECOS field had office phone numbers etc, separated
+ * with commas. Also & stands for capitalized form of the login name.
+ */
+
+ for (len = 0, dst = name, src = w->pw_gecos; len < sz; src++) {
+ int ch = *src;
+ if (ch != '&') {
+ *dst++ = ch;
+ if (ch == 0 || ch == ',')
+ break;
+ len++;
+ continue;
+ }
+ if (len + nlen < sz) {
+ /* Sorry, Mr. McDonald... */
+ *dst++ = toupper(*w->pw_name);
+ memcpy(dst, w->pw_name + 1, nlen - 1);
+ dst += nlen - 1;
+ }
+ }
+ if (len < sz)
+ name[len] = 0;
+ else
+ die("Your parents must have hated you!");
+
+}
+
+int setup_ident(void)
+{
+ int len;
+ struct passwd *pw = getpwuid(getuid());
+
+ if (!pw)
+ die("You don't exist. Go away!");
+
+ /* Get the name ("gecos") */
+ copy_gecos(pw, git_default_name, sizeof(git_default_name));
+
+ /* Make up a fake email address (name + '@' + hostname [+ '.' + domainname]) */
+ len = strlen(pw->pw_name);
+ if (len > sizeof(git_default_email)/2)
+ die("Your sysadmin must hate you!");
+ memcpy(git_default_email, pw->pw_name, len);
+ git_default_email[len++] = '@';
+ gethostname(git_default_email + len, sizeof(git_default_email) - len);
+ if (!strchr(git_default_email+len, '.')) {
+ struct hostent *he = gethostbyname(git_default_email + len);
+ char *domainname;
+
+ len = strlen(git_default_email);
+ git_default_email[len++] = '.';
+ if (he && (domainname = strchr(he->h_name, '.')))
+ strncpy(git_default_email + len, domainname + 1, sizeof(git_default_email) - len);
+ else
+ strncpy(git_default_email + len, "(none)", sizeof(git_default_email) - len);
+ git_default_email[sizeof(git_default_email) - 1] = 0;
+ }
+ /* And set the default date */
+ datestamp(git_default_date, sizeof(git_default_date));
+ return 0;
+}
+
+static int add_raw(char *buf, int size, int offset, const char *str)
+{
+ int len = strlen(str);
+ if (offset + len > size)
+ return size;
+ memcpy(buf + offset, str, len);
+ return offset + len;
+}
+
+static int crud(unsigned char c)
+{
+ static char crud_array[256];
+ static int crud_array_initialized = 0;
+
+ if (!crud_array_initialized) {
+ int k;
+
+ for (k = 0; k <= 31; ++k) crud_array[k] = 1;
+ crud_array[' '] = 1;
+ crud_array['.'] = 1;
+ crud_array[','] = 1;
+ crud_array[':'] = 1;
+ crud_array[';'] = 1;
+ crud_array['<'] = 1;
+ crud_array['>'] = 1;
+ crud_array['"'] = 1;
+ crud_array['\''] = 1;
+ crud_array_initialized = 1;
+ }
+ return crud_array[c];
+}
+
+/*
+ * Copy over a string to the destination, but avoid special
+ * characters ('\n', '<' and '>') and remove crud at the end
+ */
+static int copy(char *buf, int size, int offset, const char *src)
+{
+ int i, len;
+ unsigned char c;
+
+ /* Remove crud from the beginning.. */
+ while ((c = *src) != 0) {
+ if (!crud(c))
+ break;
+ src++;
+ }
+
+ /* Remove crud from the end.. */
+ len = strlen(src);
+ while (len > 0) {
+ c = src[len-1];
+ if (!crud(c))
+ break;
+ --len;
+ }
+
+ /*
+ * Copy the rest to the buffer, but avoid the special
+ * characters '\n' '<' and '>' that act as delimeters on
+ * a identification line
+ */
+ for (i = 0; i < len; i++) {
+ c = *src++;
+ switch (c) {
+ case '\n': case '<': case '>':
+ continue;
+ }
+ if (offset >= size)
+ return size;
+ buf[offset++] = c;
+ }
+ return offset;
+}
+
+char *get_ident(const char *name, const char *email, const char *date_str)
+{
+ static char buffer[1000];
+ char date[50];
+ int i;
+
+ if (!name)
+ name = git_default_name;
+ if (!email)
+ email = git_default_email;
+ strcpy(date, git_default_date);
+ if (date_str)
+ parse_date(date_str, date, sizeof(date));
+
+ i = copy(buffer, sizeof(buffer), 0, name);
+ i = add_raw(buffer, sizeof(buffer), i, " <");
+ i = copy(buffer, sizeof(buffer), i, email);
+ i = add_raw(buffer, sizeof(buffer), i, "> ");
+ i = copy(buffer, sizeof(buffer), i, date);
+ if (i >= sizeof(buffer))
+ die("Impossibly long personal identifier");
+ buffer[i] = 0;
+ return buffer;
+}
+
+char *git_author_info(void)
+{
+ return get_ident(getenv("GIT_AUTHOR_NAME"), getenv("GIT_AUTHOR_EMAIL"), getenv("GIT_AUTHOR_DATE"));
+}
+
+char *git_committer_info(void)
+{
+ return get_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE"));
+}
diff --git a/index-pack.c b/index-pack.c
new file mode 100644
index 000000000..785fe71a6
--- /dev/null
+++ b/index-pack.c
@@ -0,0 +1,462 @@
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+#include "csum-file.h"
+
+static const char index_pack_usage[] =
+"git-index-pack [-o index-file] pack-file";
+
+struct object_entry
+{
+ unsigned long offset;
+ enum object_type type;
+ enum object_type real_type;
+ unsigned char sha1[20];
+};
+
+struct delta_entry
+{
+ struct object_entry *obj;
+ unsigned char base_sha1[20];
+};
+
+static const char *pack_name;
+static unsigned char *pack_base;
+static unsigned long pack_size;
+static struct object_entry *objects;
+static struct delta_entry *deltas;
+static int nr_objects;
+static int nr_deltas;
+
+static void open_pack_file(void)
+{
+ int fd;
+ struct stat st;
+
+ fd = open(pack_name, O_RDONLY);
+ if (fd < 0)
+ die("cannot open packfile '%s': %s", pack_name,
+ strerror(errno));
+ if (fstat(fd, &st)) {
+ int err = errno;
+ close(fd);
+ die("cannot fstat packfile '%s': %s", pack_name,
+ strerror(err));
+ }
+ pack_size = st.st_size;
+ pack_base = mmap(NULL, pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (pack_base == MAP_FAILED) {
+ int err = errno;
+ close(fd);
+ die("cannot mmap packfile '%s': %s", pack_name,
+ strerror(err));
+ }
+ close(fd);
+}
+
+static void parse_pack_header(void)
+{
+ const struct pack_header *hdr;
+ unsigned char sha1[20];
+ SHA_CTX ctx;
+
+ /* Ensure there are enough bytes for the header and final SHA1 */
+ if (pack_size < sizeof(struct pack_header) + 20)
+ die("packfile '%s' is too small", pack_name);
+
+ /* Header consistency check */
+ hdr = (void *)pack_base;
+ if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+ die("packfile '%s' signature mismatch", pack_name);
+ if (hdr->hdr_version != htonl(PACK_VERSION))
+ die("packfile '%s' version %d different from ours %d",
+ pack_name, ntohl(hdr->hdr_version), PACK_VERSION);
+
+ nr_objects = ntohl(hdr->hdr_entries);
+
+ /* Check packfile integrity */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, pack_base, pack_size - 20);
+ SHA1_Final(sha1, &ctx);
+ if (memcmp(sha1, pack_base + pack_size - 20, 20))
+ die("packfile '%s' SHA1 mismatch", pack_name);
+}
+
+static void bad_object(unsigned long offset, const char *format,
+ ...) NORETURN __attribute__((format (printf, 2, 3)));
+
+static void bad_object(unsigned long offset, const char *format, ...)
+{
+ va_list params;
+ char buf[1024];
+
+ va_start(params, format);
+ vsnprintf(buf, sizeof(buf), format, params);
+ va_end(params);
+ die("packfile '%s': bad object at offset %lu: %s",
+ pack_name, offset, buf);
+}
+
+static void *unpack_entry_data(unsigned long offset,
+ unsigned long *current_pos, unsigned long size)
+{
+ unsigned long pack_limit = pack_size - 20;
+ unsigned long pos = *current_pos;
+ z_stream stream;
+ void *buf = xmalloc(size);
+
+ memset(&stream, 0, sizeof(stream));
+ stream.next_out = buf;
+ stream.avail_out = size;
+ stream.next_in = pack_base + pos;
+ stream.avail_in = pack_limit - pos;
+ inflateInit(&stream);
+
+ for (;;) {
+ int ret = inflate(&stream, 0);
+ if (ret == Z_STREAM_END)
+ break;
+ if (ret != Z_OK)
+ bad_object(offset, "inflate returned %d", ret);
+ }
+ inflateEnd(&stream);
+ if (stream.total_out != size)
+ bad_object(offset, "size mismatch (expected %lu, got %lu)",
+ size, stream.total_out);
+ *current_pos = pack_limit - stream.avail_in;
+ return buf;
+}
+
+static void *unpack_raw_entry(unsigned long offset,
+ enum object_type *obj_type,
+ unsigned long *obj_size,
+ unsigned char *delta_base,
+ unsigned long *next_obj_offset)
+{
+ unsigned long pack_limit = pack_size - 20;
+ unsigned long pos = offset;
+ unsigned char c;
+ unsigned long size;
+ unsigned shift;
+ enum object_type type;
+ void *data;
+
+ c = pack_base[pos++];
+ type = (c >> 4) & 7;
+ size = (c & 15);
+ shift = 4;
+ while (c & 0x80) {
+ if (pos >= pack_limit)
+ bad_object(offset, "object extends past end of pack");
+ c = pack_base[pos++];
+ size += (c & 0x7fUL) << shift;
+ shift += 7;
+ }
+
+ switch (type) {
+ case OBJ_DELTA:
+ if (pos + 20 >= pack_limit)
+ bad_object(offset, "object extends past end of pack");
+ memcpy(delta_base, pack_base + pos, 20);
+ pos += 20;
+ /* fallthru */
+ case OBJ_COMMIT:
+ case OBJ_TREE:
+ case OBJ_BLOB:
+ case OBJ_TAG:
+ data = unpack_entry_data(offset, &pos, size);
+ break;
+ default:
+ bad_object(offset, "bad object type %d", type);
+ }
+
+ *obj_type = type;
+ *obj_size = size;
+ *next_obj_offset = pos;
+ return data;
+}
+
+static int find_delta(const unsigned char *base_sha1)
+{
+ int first = 0, last = nr_deltas;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct delta_entry *delta = &deltas[next];
+ int cmp;
+
+ cmp = memcmp(base_sha1, delta->base_sha1, 20);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
+}
+
+static int find_deltas_based_on_sha1(const unsigned char *base_sha1,
+ int *first_index, int *last_index)
+{
+ int first = find_delta(base_sha1);
+ int last = first;
+ int end = nr_deltas - 1;
+
+ if (first < 0)
+ return -1;
+ while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20))
+ --first;
+ while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20))
+ ++last;
+ *first_index = first;
+ *last_index = last;
+ return 0;
+}
+
+static void sha1_object(const void *data, unsigned long size,
+ enum object_type type, unsigned char *sha1)
+{
+ SHA_CTX ctx;
+ char header[50];
+ int header_size;
+ const char *type_str;
+
+ switch (type) {
+ case OBJ_COMMIT: type_str = "commit"; break;
+ case OBJ_TREE: type_str = "tree"; break;
+ case OBJ_BLOB: type_str = "blob"; break;
+ case OBJ_TAG: type_str = "tag"; break;
+ default:
+ die("bad type %d", type);
+ }
+
+ header_size = sprintf(header, "%s %lu", type_str, size) + 1;
+
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, header, header_size);
+ SHA1_Update(&ctx, data, size);
+ SHA1_Final(sha1, &ctx);
+}
+
+static void resolve_delta(struct delta_entry *delta, void *base_data,
+ unsigned long base_size, enum object_type type)
+{
+ struct object_entry *obj = delta->obj;
+ void *delta_data;
+ unsigned long delta_size;
+ void *result;
+ unsigned long result_size;
+ enum object_type delta_type;
+ unsigned char base_sha1[20];
+ unsigned long next_obj_offset;
+ int j, first, last;
+
+ obj->real_type = type;
+ delta_data = unpack_raw_entry(obj->offset, &delta_type,
+ &delta_size, base_sha1,
+ &next_obj_offset);
+ result = patch_delta(base_data, base_size, delta_data, delta_size,
+ &result_size);
+ free(delta_data);
+ if (!result)
+ bad_object(obj->offset, "failed to apply delta");
+ sha1_object(result, result_size, type, obj->sha1);
+ if (!find_deltas_based_on_sha1(obj->sha1, &first, &last)) {
+ for (j = first; j <= last; j++)
+ resolve_delta(&deltas[j], result, result_size, type);
+ }
+ free(result);
+}
+
+static int compare_delta_entry(const void *a, const void *b)
+{
+ const struct delta_entry *delta_a = a;
+ const struct delta_entry *delta_b = b;
+ return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20);
+}
+
+static void parse_pack_objects(void)
+{
+ int i;
+ unsigned long offset = sizeof(struct pack_header);
+ unsigned char base_sha1[20];
+ void *data;
+ unsigned long data_size;
+
+ /*
+ * First pass:
+ * - find locations of all objects;
+ * - calculate SHA1 of all non-delta objects;
+ * - remember base SHA1 for all deltas.
+ */
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = &objects[i];
+ obj->offset = offset;
+ data = unpack_raw_entry(offset, &obj->type, &data_size,
+ base_sha1, &offset);
+ obj->real_type = obj->type;
+ if (obj->type == OBJ_DELTA) {
+ struct delta_entry *delta = &deltas[nr_deltas++];
+ delta->obj = obj;
+ memcpy(delta->base_sha1, base_sha1, 20);
+ } else
+ sha1_object(data, data_size, obj->type, obj->sha1);
+ free(data);
+ }
+ if (offset != pack_size - 20)
+ die("packfile '%s' has junk at the end", pack_name);
+
+ /* Sort deltas by base SHA1 for fast searching */
+ qsort(deltas, nr_deltas, sizeof(struct delta_entry),
+ compare_delta_entry);
+
+ /*
+ * Second pass:
+ * - for all non-delta objects, look if it is used as a base for
+ * deltas;
+ * - if used as a base, uncompress the object and apply all deltas,
+ * recursively checking if the resulting object is used as a base
+ * for some more deltas.
+ */
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = &objects[i];
+ int j, first, last;
+
+ if (obj->type == OBJ_DELTA)
+ continue;
+ if (find_deltas_based_on_sha1(obj->sha1, &first, &last))
+ continue;
+ data = unpack_raw_entry(obj->offset, &obj->type, &data_size,
+ base_sha1, &offset);
+ for (j = first; j <= last; j++)
+ resolve_delta(&deltas[j], data, data_size, obj->type);
+ free(data);
+ }
+
+ /* Check for unresolved deltas */
+ for (i = 0; i < nr_deltas; i++) {
+ if (deltas[i].obj->real_type == OBJ_DELTA)
+ die("packfile '%s' has unresolved deltas", pack_name);
+ }
+}
+
+static int sha1_compare(const void *_a, const void *_b)
+{
+ struct object_entry *a = *(struct object_entry **)_a;
+ struct object_entry *b = *(struct object_entry **)_b;
+ return memcmp(a->sha1, b->sha1, 20);
+}
+
+static void write_index_file(const char *index_name, unsigned char *sha1)
+{
+ struct sha1file *f;
+ struct object_entry **sorted_by_sha =
+ xcalloc(nr_objects, sizeof(struct object_entry *));
+ struct object_entry **list = sorted_by_sha;
+ struct object_entry **last = sorted_by_sha + nr_objects;
+ unsigned int array[256];
+ int i;
+ SHA_CTX ctx;
+
+ for (i = 0; i < nr_objects; ++i)
+ sorted_by_sha[i] = &objects[i];
+ qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
+ sha1_compare);
+
+ unlink(index_name);
+ f = sha1create("%s", index_name);
+
+ /*
+ * Write the first-level table (the list is sorted,
+ * but we use a 256-entry lookup to be able to avoid
+ * having to do eight extra binary search iterations).
+ */
+ for (i = 0; i < 256; i++) {
+ struct object_entry **next = list;
+ while (next < last) {
+ struct object_entry *obj = *next;
+ if (obj->sha1[0] != i)
+ break;
+ next++;
+ }
+ array[i] = htonl(next - sorted_by_sha);
+ list = next;
+ }
+ sha1write(f, array, 256 * sizeof(int));
+
+ /* recompute the SHA1 hash of sorted object names.
+ * currently pack-objects does not do this, but that
+ * can be fixed.
+ */
+ SHA1_Init(&ctx);
+ /*
+ * Write the actual SHA1 entries..
+ */
+ list = sorted_by_sha;
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *obj = *list++;
+ unsigned int offset = htonl(obj->offset);
+ sha1write(f, &offset, 4);
+ sha1write(f, obj->sha1, 20);
+ SHA1_Update(&ctx, obj->sha1, 20);
+ }
+ sha1write(f, pack_base + pack_size - 20, 20);
+ sha1close(f, NULL, 1);
+ free(sorted_by_sha);
+ SHA1_Final(sha1, &ctx);
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ char *index_name = NULL;
+ char *index_name_buf = NULL;
+ unsigned char sha1[20];
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "-o")) {
+ if (index_name || (i+1) >= argc)
+ usage(index_pack_usage);
+ index_name = argv[++i];
+ } else
+ usage(index_pack_usage);
+ continue;
+ }
+
+ if (pack_name)
+ usage(index_pack_usage);
+ pack_name = arg;
+ }
+
+ if (!pack_name)
+ usage(index_pack_usage);
+ if (!index_name) {
+ int len = strlen(pack_name);
+ if (len < 5 || strcmp(pack_name + len - 5, ".pack"))
+ die("packfile name '%s' does not end with '.pack'",
+ pack_name);
+ index_name_buf = xmalloc(len - 1);
+ memcpy(index_name_buf, pack_name, len - 5);
+ strcpy(index_name_buf + len - 5, ".idx");
+ index_name = index_name_buf;
+ }
+
+ open_pack_file();
+ parse_pack_header();
+ objects = xcalloc(nr_objects, sizeof(struct object_entry));
+ deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
+ parse_pack_objects();
+ free(deltas);
+ write_index_file(index_name, sha1);
+ free(objects);
+ free(index_name_buf);
+
+ printf("%s\n", sha1_to_hex(sha1));
+
+ return 0;
+}
diff --git a/index.c b/index.c
new file mode 100644
index 000000000..ad0eafedc
--- /dev/null
+++ b/index.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2005, Junio C Hamano
+ */
+#include <signal.h>
+#include "cache.h"
+
+static struct cache_file *cache_file_list;
+
+static void remove_lock_file(void)
+{
+ while (cache_file_list) {
+ if (cache_file_list->lockfile[0])
+ unlink(cache_file_list->lockfile);
+ cache_file_list = cache_file_list->next;
+ }
+}
+
+static void remove_lock_file_on_signal(int signo)
+{
+ remove_lock_file();
+}
+
+int hold_index_file_for_update(struct cache_file *cf, const char *path)
+{
+ int fd;
+ sprintf(cf->lockfile, "%s.lock", path);
+ fd = open(cf->lockfile, O_RDWR | O_CREAT | O_EXCL, 0666);
+ if (fd >=0 && !cf->next) {
+ cf->next = cache_file_list;
+ cache_file_list = cf;
+ signal(SIGINT, remove_lock_file_on_signal);
+ atexit(remove_lock_file);
+ }
+ return fd;
+}
+
+int commit_index_file(struct cache_file *cf)
+{
+ char indexfile[PATH_MAX];
+ int i;
+ strcpy(indexfile, cf->lockfile);
+ i = strlen(indexfile) - 5; /* .lock */
+ indexfile[i] = 0;
+ i = rename(cf->lockfile, indexfile);
+ cf->lockfile[0] = 0;
+ return i;
+}
+
+void rollback_index_file(struct cache_file *cf)
+{
+ if (cf->lockfile[0])
+ unlink(cf->lockfile);
+ cf->lockfile[0] = 0;
+}
+
diff --git a/init-db.c b/init-db.c
new file mode 100644
index 000000000..bd88291b0
--- /dev/null
+++ b/init-db.c
@@ -0,0 +1,268 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+#ifndef DEFAULT_GIT_TEMPLATE_DIR
+#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates/"
+#endif
+
+static void safe_create_dir(const char *dir)
+{
+ if (mkdir(dir, 0777) < 0) {
+ if (errno != EEXIST) {
+ perror(dir);
+ exit(1);
+ }
+ }
+}
+
+static int copy_file(const char *dst, const char *src, int mode)
+{
+ int fdi, fdo, status;
+
+ mode = (mode & 0111) ? 0777 : 0666;
+ if ((fdi = open(src, O_RDONLY)) < 0)
+ return fdi;
+ if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
+ close(fdi);
+ return fdo;
+ }
+ status = copy_fd(fdi, fdo);
+ close(fdo);
+ return status;
+}
+
+static void copy_templates_1(char *path, int baselen,
+ char *template, int template_baselen,
+ DIR *dir)
+{
+ struct dirent *de;
+
+ /* Note: if ".git/hooks" file exists in the repository being
+ * re-initialized, /etc/core-git/templates/hooks/update would
+ * cause git-init-db to fail here. I think this is sane but
+ * it means that the set of templates we ship by default, along
+ * with the way the namespace under .git/ is organized, should
+ * be really carefully chosen.
+ */
+ safe_create_dir(path);
+ while ((de = readdir(dir)) != NULL) {
+ struct stat st_git, st_template;
+ int namelen;
+ int exists = 0;
+
+ if (de->d_name[0] == '.')
+ continue;
+ namelen = strlen(de->d_name);
+ if ((PATH_MAX <= baselen + namelen) ||
+ (PATH_MAX <= template_baselen + namelen))
+ die("insanely long template name %s", de->d_name);
+ memcpy(path + baselen, de->d_name, namelen+1);
+ memcpy(template + template_baselen, de->d_name, namelen+1);
+ if (lstat(path, &st_git)) {
+ if (errno != ENOENT)
+ die("cannot stat %s", path);
+ }
+ else
+ exists = 1;
+
+ if (lstat(template, &st_template))
+ die("cannot stat template %s", template);
+
+ if (S_ISDIR(st_template.st_mode)) {
+ DIR *subdir = opendir(template);
+ int baselen_sub = baselen + namelen;
+ int template_baselen_sub = template_baselen + namelen;
+ if (!subdir)
+ die("cannot opendir %s", template);
+ path[baselen_sub++] =
+ template[template_baselen_sub++] = '/';
+ path[baselen_sub] =
+ template[template_baselen_sub] = 0;
+ copy_templates_1(path, baselen_sub,
+ template, template_baselen_sub,
+ subdir);
+ closedir(subdir);
+ }
+ else if (exists)
+ continue;
+ else if (S_ISLNK(st_template.st_mode)) {
+ char lnk[256];
+ int len;
+ len = readlink(template, lnk, sizeof(lnk));
+ if (len < 0)
+ die("cannot readlink %s", template);
+ if (sizeof(lnk) <= len)
+ die("insanely long symlink %s", template);
+ lnk[len] = 0;
+ if (symlink(lnk, path))
+ die("cannot symlink %s %s", lnk, path);
+ }
+ else if (S_ISREG(st_template.st_mode)) {
+ if (copy_file(path, template, st_template.st_mode))
+ die("cannot copy %s to %s", template, path);
+ }
+ else
+ error("ignoring template %s", template);
+ }
+}
+
+static void copy_templates(const char *git_dir, int len, char *template_dir)
+{
+ char path[PATH_MAX];
+ char template_path[PATH_MAX];
+ int template_len;
+ DIR *dir;
+
+ if (!template_dir)
+ template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+ strcpy(template_path, template_dir);
+ template_len = strlen(template_path);
+ if (template_path[template_len-1] != '/') {
+ template_path[template_len++] = '/';
+ template_path[template_len] = 0;
+ }
+ dir = opendir(template_path);
+ if (!dir) {
+ fprintf(stderr, "warning: templates not found %s\n",
+ template_dir);
+ return;
+ }
+
+ memcpy(path, git_dir, len);
+ path[len] = 0;
+ copy_templates_1(path, len,
+ template_path, template_len,
+ dir);
+ closedir(dir);
+}
+
+static void create_default_files(const char *git_dir,
+ char *template_path)
+{
+ unsigned len = strlen(git_dir);
+ static char path[PATH_MAX];
+ unsigned char sha1[20];
+
+ if (len > sizeof(path)-50)
+ die("insane git directory %s", git_dir);
+ memcpy(path, git_dir, len);
+
+ if (len && path[len-1] != '/')
+ path[len++] = '/';
+
+ /*
+ * Create .git/refs/{heads,tags}
+ */
+ strcpy(path + len, "refs");
+ safe_create_dir(path);
+ strcpy(path + len, "refs/heads");
+ safe_create_dir(path);
+ strcpy(path + len, "refs/tags");
+ safe_create_dir(path);
+
+ /*
+ * Create the default symlink from ".git/HEAD" to the "master"
+ * branch, if it does not exist yet.
+ */
+ strcpy(path + len, "HEAD");
+ if (read_ref(path, sha1) < 0) {
+ if (create_symref(path, "refs/heads/master") < 0)
+ exit(1);
+ }
+ path[len] = 0;
+ copy_templates(path, len, template_path);
+
+ /*
+ * Find out if we can trust the executable bit.
+ */
+ safe_create_dir(path);
+ strcpy(path + len, "config");
+ if (access(path, R_OK) < 0) {
+ static const char contents[] =
+ "#\n"
+ "# This is the config file\n"
+ "#\n"
+ "\n"
+ "; core variables\n"
+ "[core]\n"
+ " ; Don't trust file modes\n"
+ " filemode = false\n"
+ "\n";
+ FILE *config = fopen(path, "w");
+ struct stat st;
+
+ if (!config)
+ die("Can not write to %s?", path);
+
+ fwrite(contents, sizeof(contents)-1, 1, config);
+
+ fclose(config);
+
+ if (!lstat(path, &st)) {
+ struct stat st2;
+ if (!chmod(path, st.st_mode ^ S_IXUSR) &&
+ !lstat(path, &st2) &&
+ st.st_mode != st2.st_mode)
+ unlink(path);
+ else
+ fprintf(stderr, "Ignoring file modes\n");
+ }
+ }
+}
+
+static const char init_db_usage[] =
+"git-init-db [--template=<template-directory>]";
+
+/*
+ * If you want to, you can share the DB area with any number of branches.
+ * That has advantages: you can save space by sharing all the SHA1 objects.
+ * On the other hand, it might just make lookup slower and messier. You
+ * be the judge. The default case is to have one DB per managed directory.
+ */
+int main(int argc, char **argv)
+{
+ const char *git_dir;
+ const char *sha1_dir;
+ char *path, *template_dir = NULL;
+ int len, i;
+
+ for (i = 1; i < argc; i++, argv++) {
+ char *arg = argv[1];
+ if (arg[0] != '-')
+ break;
+ else if (!strncmp(arg, "--template=", 11))
+ template_dir = arg+11;
+ else
+ die(init_db_usage);
+ }
+
+ /*
+ * Set up the default .git directory contents
+ */
+ git_dir = getenv(GIT_DIR_ENVIRONMENT);
+ if (!git_dir) {
+ git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
+ fprintf(stderr, "defaulting to local storage area\n");
+ }
+ safe_create_dir(git_dir);
+ create_default_files(git_dir, template_dir);
+
+ /*
+ * And set up the object store.
+ */
+ sha1_dir = get_object_directory();
+ len = strlen(sha1_dir);
+ path = xmalloc(len + 40);
+ memcpy(path, sha1_dir, len);
+
+ safe_create_dir(sha1_dir);
+ strcpy(path+len, "/pack");
+ safe_create_dir(path);
+ strcpy(path+len, "/info");
+ safe_create_dir(path);
+ return 0;
+}
diff --git a/local-fetch.c b/local-fetch.c
new file mode 100644
index 000000000..093110914
--- /dev/null
+++ b/local-fetch.c
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "fetch.h"
+
+static int use_link = 0;
+static int use_symlink = 0;
+static int use_filecopy = 1;
+
+static char *path; /* "Remote" git repository */
+
+void prefetch(unsigned char *sha1)
+{
+}
+
+static struct packed_git *packs = NULL;
+
+static void setup_index(unsigned char *sha1)
+{
+ struct packed_git *new_pack;
+ char filename[PATH_MAX];
+ strcpy(filename, path);
+ strcat(filename, "/objects/pack/pack-");
+ strcat(filename, sha1_to_hex(sha1));
+ strcat(filename, ".idx");
+ new_pack = parse_pack_index_file(sha1, filename);
+ new_pack->next = packs;
+ packs = new_pack;
+}
+
+static int setup_indices(void)
+{
+ DIR *dir;
+ struct dirent *de;
+ char filename[PATH_MAX];
+ unsigned char sha1[20];
+ sprintf(filename, "%s/objects/pack/", path);
+ dir = opendir(filename);
+ if (!dir)
+ return -1;
+ while ((de = readdir(dir)) != NULL) {
+ int namelen = strlen(de->d_name);
+ if (namelen != 50 ||
+ strcmp(de->d_name + namelen - 5, ".pack"))
+ continue;
+ get_sha1_hex(de->d_name + 5, sha1);
+ setup_index(sha1);
+ }
+ closedir(dir);
+ return 0;
+}
+
+static int copy_file(const char *source, char *dest, const char *hex,
+ int warn_if_not_exists)
+{
+ safe_create_leading_directories(dest);
+ if (use_link) {
+ if (!link(source, dest)) {
+ pull_say("link %s\n", hex);
+ return 0;
+ }
+ /* If we got ENOENT there is no point continuing. */
+ if (errno == ENOENT) {
+ if (warn_if_not_exists)
+ fprintf(stderr, "does not exist %s\n", source);
+ return -1;
+ }
+ }
+ if (use_symlink) {
+ struct stat st;
+ if (stat(source, &st)) {
+ if (!warn_if_not_exists && errno == ENOENT)
+ return -1;
+ fprintf(stderr, "cannot stat %s: %s\n", source,
+ strerror(errno));
+ return -1;
+ }
+ if (!symlink(source, dest)) {
+ pull_say("symlink %s\n", hex);
+ return 0;
+ }
+ }
+ if (use_filecopy) {
+ int ifd, ofd, status = 0;
+
+ ifd = open(source, O_RDONLY);
+ if (ifd < 0) {
+ if (!warn_if_not_exists && errno == ENOENT)
+ return -1;
+ fprintf(stderr, "cannot open %s\n", source);
+ return -1;
+ }
+ ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (ofd < 0) {
+ fprintf(stderr, "cannot open %s\n", dest);
+ close(ifd);
+ return -1;
+ }
+ status = copy_fd(ifd, ofd);
+ close(ofd);
+ if (status)
+ fprintf(stderr, "cannot write %s\n", dest);
+ else
+ pull_say("copy %s\n", hex);
+ return status;
+ }
+ fprintf(stderr, "failed to copy %s with given copy methods.\n", hex);
+ return -1;
+}
+
+static int fetch_pack(const unsigned char *sha1)
+{
+ struct packed_git *target;
+ char filename[PATH_MAX];
+ if (setup_indices())
+ return -1;
+ target = find_sha1_pack(sha1, packs);
+ if (!target)
+ return error("Couldn't find %s: not separate or in any pack",
+ sha1_to_hex(sha1));
+ if (get_verbosely) {
+ fprintf(stderr, "Getting pack %s\n",
+ sha1_to_hex(target->sha1));
+ fprintf(stderr, " which contains %s\n",
+ sha1_to_hex(sha1));
+ }
+ sprintf(filename, "%s/objects/pack/pack-%s.pack",
+ path, sha1_to_hex(target->sha1));
+ copy_file(filename, sha1_pack_name(target->sha1),
+ sha1_to_hex(target->sha1), 1);
+ sprintf(filename, "%s/objects/pack/pack-%s.idx",
+ path, sha1_to_hex(target->sha1));
+ copy_file(filename, sha1_pack_index_name(target->sha1),
+ sha1_to_hex(target->sha1), 1);
+ install_packed_git(target);
+ return 0;
+}
+
+static int fetch_file(const unsigned char *sha1)
+{
+ static int object_name_start = -1;
+ static char filename[PATH_MAX];
+ char *hex = sha1_to_hex(sha1);
+ char *dest_filename = sha1_file_name(sha1);
+
+ if (object_name_start < 0) {
+ strcpy(filename, path); /* e.g. git.git */
+ strcat(filename, "/objects/");
+ object_name_start = strlen(filename);
+ }
+ filename[object_name_start+0] = hex[0];
+ filename[object_name_start+1] = hex[1];
+ filename[object_name_start+2] = '/';
+ strcpy(filename + object_name_start + 3, hex + 2);
+ return copy_file(filename, dest_filename, hex, 0);
+}
+
+int fetch(unsigned char *sha1)
+{
+ if (has_sha1_file(sha1))
+ return 0;
+ else
+ return fetch_file(sha1) && fetch_pack(sha1);
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+ static int ref_name_start = -1;
+ static char filename[PATH_MAX];
+ static char hex[41];
+ int ifd;
+
+ if (ref_name_start < 0) {
+ sprintf(filename, "%s/refs/", path);
+ ref_name_start = strlen(filename);
+ }
+ strcpy(filename + ref_name_start, ref);
+ ifd = open(filename, O_RDONLY);
+ if (ifd < 0) {
+ close(ifd);
+ fprintf(stderr, "cannot open %s\n", filename);
+ return -1;
+ }
+ if (read(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) {
+ close(ifd);
+ fprintf(stderr, "cannot read from %s\n", filename);
+ return -1;
+ }
+ close(ifd);
+ pull_say("ref %s\n", sha1_to_hex(sha1));
+ return 0;
+}
+
+static const char local_pull_usage[] =
+"git-local-fetch [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [-l] [-s] [-n] commit-id path";
+
+/*
+ * By default we only use file copy.
+ * If -l is specified, a hard link is attempted.
+ * If -s is specified, then a symlink is attempted.
+ * If -n is _not_ specified, then a regular file-to-file copy is done.
+ */
+int main(int argc, char **argv)
+{
+ char *commit_id;
+ int arg = 1;
+
+ while (arg < argc && argv[arg][0] == '-') {
+ if (argv[arg][1] == 't')
+ get_tree = 1;
+ else if (argv[arg][1] == 'c')
+ get_history = 1;
+ else if (argv[arg][1] == 'a') {
+ get_all = 1;
+ get_tree = 1;
+ get_history = 1;
+ }
+ else if (argv[arg][1] == 'l')
+ use_link = 1;
+ else if (argv[arg][1] == 's')
+ use_symlink = 1;
+ else if (argv[arg][1] == 'n')
+ use_filecopy = 0;
+ else if (argv[arg][1] == 'v')
+ get_verbosely = 1;
+ else if (argv[arg][1] == 'w')
+ write_ref = argv[++arg];
+ else if (!strcmp(argv[arg], "--recover"))
+ get_recover = 1;
+ else
+ usage(local_pull_usage);
+ arg++;
+ }
+ if (argc < arg + 2)
+ usage(local_pull_usage);
+ commit_id = argv[arg];
+ path = argv[arg + 1];
+
+ if (pull(commit_id))
+ return 1;
+
+ return 0;
+}
diff --git a/ls-files.c b/ls-files.c
new file mode 100644
index 000000000..db2288aee
--- /dev/null
+++ b/ls-files.c
@@ -0,0 +1,687 @@
+/*
+ * This merges the file listing in the directory cache index
+ * with the actual working directory list, and shows different
+ * combinations of the two.
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include <dirent.h>
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "quote.h"
+
+static int show_deleted = 0;
+static int show_cached = 0;
+static int show_others = 0;
+static int show_ignored = 0;
+static int show_stage = 0;
+static int show_unmerged = 0;
+static int show_modified = 0;
+static int show_killed = 0;
+static int line_terminator = '\n';
+
+static int prefix_len = 0, prefix_offset = 0;
+static const char *prefix = NULL;
+static const char **pathspec = NULL;
+
+static const char *tag_cached = "";
+static const char *tag_unmerged = "";
+static const char *tag_removed = "";
+static const char *tag_other = "";
+static const char *tag_killed = "";
+static const char *tag_modified = "";
+
+static const char *exclude_per_dir = NULL;
+
+/* We maintain three exclude pattern lists:
+ * EXC_CMDL lists patterns explicitly given on the command line.
+ * EXC_DIRS lists patterns obtained from per-directory ignore files.
+ * EXC_FILE lists patterns from fallback ignore files.
+ */
+#define EXC_CMDL 0
+#define EXC_DIRS 1
+#define EXC_FILE 2
+static struct exclude_list {
+ int nr;
+ int alloc;
+ struct exclude {
+ const char *pattern;
+ const char *base;
+ int baselen;
+ } **excludes;
+} exclude_list[3];
+
+static void add_exclude(const char *string, const char *base,
+ int baselen, struct exclude_list *which)
+{
+ struct exclude *x = xmalloc(sizeof (*x));
+
+ x->pattern = string;
+ x->base = base;
+ x->baselen = baselen;
+ if (which->nr == which->alloc) {
+ which->alloc = alloc_nr(which->alloc);
+ which->excludes = realloc(which->excludes,
+ which->alloc * sizeof(x));
+ }
+ which->excludes[which->nr++] = x;
+}
+
+static int add_excludes_from_file_1(const char *fname,
+ const char *base,
+ int baselen,
+ struct exclude_list *which)
+{
+ int fd, i;
+ long size;
+ char *buf, *entry;
+
+ fd = open(fname, O_RDONLY);
+ if (fd < 0)
+ goto err;
+ size = lseek(fd, 0, SEEK_END);
+ if (size < 0)
+ goto err;
+ lseek(fd, 0, SEEK_SET);
+ if (size == 0) {
+ close(fd);
+ return 0;
+ }
+ buf = xmalloc(size);
+ if (read(fd, buf, size) != size)
+ goto err;
+ close(fd);
+
+ entry = buf;
+ for (i = 0; i < size; i++) {
+ if (buf[i] == '\n') {
+ if (entry != buf + i && entry[0] != '#') {
+ buf[i - (i && buf[i-1] == '\r')] = 0;
+ add_exclude(entry, base, baselen, which);
+ }
+ entry = buf + i + 1;
+ }
+ }
+ return 0;
+
+ err:
+ if (0 <= fd)
+ close(fd);
+ return -1;
+}
+
+static void add_excludes_from_file(const char *fname)
+{
+ if (add_excludes_from_file_1(fname, "", 0,
+ &exclude_list[EXC_FILE]) < 0)
+ die("cannot use %s as an exclude file", fname);
+}
+
+static int push_exclude_per_directory(const char *base, int baselen)
+{
+ char exclude_file[PATH_MAX];
+ struct exclude_list *el = &exclude_list[EXC_DIRS];
+ int current_nr = el->nr;
+
+ if (exclude_per_dir) {
+ memcpy(exclude_file, base, baselen);
+ strcpy(exclude_file + baselen, exclude_per_dir);
+ add_excludes_from_file_1(exclude_file, base, baselen, el);
+ }
+ return current_nr;
+}
+
+static void pop_exclude_per_directory(int stk)
+{
+ struct exclude_list *el = &exclude_list[EXC_DIRS];
+
+ while (stk < el->nr)
+ free(el->excludes[--el->nr]);
+}
+
+/* Scan the list and let the last match determines the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+static int excluded_1(const char *pathname,
+ int pathlen,
+ struct exclude_list *el)
+{
+ int i;
+
+ if (el->nr) {
+ for (i = el->nr - 1; 0 <= i; i--) {
+ struct exclude *x = el->excludes[i];
+ const char *exclude = x->pattern;
+ int to_exclude = 1;
+
+ if (*exclude == '!') {
+ to_exclude = 0;
+ exclude++;
+ }
+
+ if (!strchr(exclude, '/')) {
+ /* match basename */
+ const char *basename = strrchr(pathname, '/');
+ basename = (basename) ? basename+1 : pathname;
+ if (fnmatch(exclude, basename, 0) == 0)
+ return to_exclude;
+ }
+ else {
+ /* match with FNM_PATHNAME:
+ * exclude has base (baselen long) inplicitly
+ * in front of it.
+ */
+ int baselen = x->baselen;
+ if (*exclude == '/')
+ exclude++;
+
+ if (pathlen < baselen ||
+ (baselen && pathname[baselen-1] != '/') ||
+ strncmp(pathname, x->base, baselen))
+ continue;
+
+ if (fnmatch(exclude, pathname+baselen,
+ FNM_PATHNAME) == 0)
+ return to_exclude;
+ }
+ }
+ }
+ return -1; /* undecided */
+}
+
+static int excluded(const char *pathname)
+{
+ int pathlen = strlen(pathname);
+ int st;
+
+ for (st = EXC_CMDL; st <= EXC_FILE; st++) {
+ switch (excluded_1(pathname, pathlen, &exclude_list[st])) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+struct nond_on_fs {
+ int len;
+ char name[0];
+};
+
+static struct nond_on_fs **dir;
+static int nr_dir;
+static int dir_alloc;
+
+static void add_name(const char *pathname, int len)
+{
+ struct nond_on_fs *ent;
+
+ if (cache_name_pos(pathname, len) >= 0)
+ return;
+
+ if (nr_dir == dir_alloc) {
+ dir_alloc = alloc_nr(dir_alloc);
+ dir = xrealloc(dir, dir_alloc*sizeof(ent));
+ }
+ ent = xmalloc(sizeof(*ent) + len + 1);
+ ent->len = len;
+ memcpy(ent->name, pathname, len);
+ ent->name[len] = 0;
+ dir[nr_dir++] = ent;
+}
+
+/*
+ * Read a directory tree. We currently ignore anything but
+ * directories, regular files and symlinks. That's because git
+ * doesn't handle them at all yet. Maybe that will change some
+ * day.
+ *
+ * Also, we ignore the name ".git" (even if it is not a directory).
+ * That likely will not change.
+ */
+static void read_directory(const char *path, const char *base, int baselen)
+{
+ DIR *dir = opendir(path);
+
+ if (dir) {
+ int exclude_stk;
+ struct dirent *de;
+ char fullname[MAXPATHLEN + 1];
+ memcpy(fullname, base, baselen);
+
+ exclude_stk = push_exclude_per_directory(base, baselen);
+
+ while ((de = readdir(dir)) != NULL) {
+ int len;
+
+ if ((de->d_name[0] == '.') &&
+ (de->d_name[1] == 0 ||
+ !strcmp(de->d_name + 1, ".") ||
+ !strcmp(de->d_name + 1, "git")))
+ continue;
+ len = strlen(de->d_name);
+ memcpy(fullname + baselen, de->d_name, len+1);
+ if (excluded(fullname) != show_ignored)
+ continue;
+
+ switch (DTYPE(de)) {
+ struct stat st;
+ default:
+ continue;
+ case DT_UNKNOWN:
+ if (lstat(fullname, &st))
+ continue;
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ break;
+ if (!S_ISDIR(st.st_mode))
+ continue;
+ /* fallthrough */
+ case DT_DIR:
+ memcpy(fullname + baselen + len, "/", 2);
+ read_directory(fullname, fullname,
+ baselen + len + 1);
+ continue;
+ case DT_REG:
+ case DT_LNK:
+ break;
+ }
+ add_name(fullname, baselen + len);
+ }
+ closedir(dir);
+
+ pop_exclude_per_directory(exclude_stk);
+ }
+}
+
+static int cmp_name(const void *p1, const void *p2)
+{
+ const struct nond_on_fs *e1 = *(const struct nond_on_fs **)p1;
+ const struct nond_on_fs *e2 = *(const struct nond_on_fs **)p2;
+
+ return cache_name_compare(e1->name, e1->len,
+ e2->name, e2->len);
+}
+
+/*
+ * Match a pathspec against a filename. The first "len" characters
+ * are the common prefix
+ */
+static int match(const char **spec, const char *filename, int len)
+{
+ const char *m;
+
+ while ((m = *spec++) != NULL) {
+ int matchlen = strlen(m + len);
+
+ if (!matchlen)
+ return 1;
+ if (!strncmp(m + len, filename + len, matchlen)) {
+ if (m[len + matchlen - 1] == '/')
+ return 1;
+ switch (filename[len + matchlen]) {
+ case '/': case '\0':
+ return 1;
+ }
+ }
+ if (!fnmatch(m + len, filename + len, 0))
+ return 1;
+ }
+ return 0;
+}
+
+static void show_dir_entry(const char *tag, struct nond_on_fs *ent)
+{
+ int len = prefix_len;
+ int offset = prefix_offset;
+
+ if (len >= ent->len)
+ die("git-ls-files: internal error - directory entry not superset of prefix");
+
+ if (pathspec && !match(pathspec, ent->name, len))
+ return;
+
+ fputs(tag, stdout);
+ write_name_quoted("", ent->name + offset, line_terminator, stdout);
+ putchar(line_terminator);
+}
+
+static void show_other_files(void)
+{
+ int i;
+ for (i = 0; i < nr_dir; i++) {
+ /* We should not have a matching entry, but we
+ * may have an unmerged entry for this path.
+ */
+ struct nond_on_fs *ent = dir[i];
+ int pos = cache_name_pos(ent->name, ent->len);
+ struct cache_entry *ce;
+ if (0 <= pos)
+ die("bug in show-other-files");
+ pos = -pos - 1;
+ if (pos < active_nr) {
+ ce = active_cache[pos];
+ if (ce_namelen(ce) == ent->len &&
+ !memcmp(ce->name, ent->name, ent->len))
+ continue; /* Yup, this one exists unmerged */
+ }
+ show_dir_entry(tag_other, ent);
+ }
+}
+
+static void show_killed_files(void)
+{
+ int i;
+ for (i = 0; i < nr_dir; i++) {
+ struct nond_on_fs *ent = dir[i];
+ char *cp, *sp;
+ int pos, len, killed = 0;
+
+ for (cp = ent->name; cp - ent->name < ent->len; cp = sp + 1) {
+ sp = strchr(cp, '/');
+ if (!sp) {
+ /* If ent->name is prefix of an entry in the
+ * cache, it will be killed.
+ */
+ pos = cache_name_pos(ent->name, ent->len);
+ if (0 <= pos)
+ die("bug in show-killed-files");
+ pos = -pos - 1;
+ while (pos < active_nr &&
+ ce_stage(active_cache[pos]))
+ pos++; /* skip unmerged */
+ if (active_nr <= pos)
+ break;
+ /* pos points at a name immediately after
+ * ent->name in the cache. Does it expect
+ * ent->name to be a directory?
+ */
+ len = ce_namelen(active_cache[pos]);
+ if ((ent->len < len) &&
+ !strncmp(active_cache[pos]->name,
+ ent->name, ent->len) &&
+ active_cache[pos]->name[ent->len] == '/')
+ killed = 1;
+ break;
+ }
+ if (0 <= cache_name_pos(ent->name, sp - ent->name)) {
+ /* If any of the leading directories in
+ * ent->name is registered in the cache,
+ * ent->name will be killed.
+ */
+ killed = 1;
+ break;
+ }
+ }
+ if (killed)
+ show_dir_entry(tag_killed, dir[i]);
+ }
+}
+
+static void show_ce_entry(const char *tag, struct cache_entry *ce)
+{
+ int len = prefix_len;
+ int offset = prefix_offset;
+
+ if (len >= ce_namelen(ce))
+ die("git-ls-files: internal error - cache entry not superset of prefix");
+
+ if (pathspec && !match(pathspec, ce->name, len))
+ return;
+
+ if (!show_stage) {
+ fputs(tag, stdout);
+ write_name_quoted("", ce->name + offset, line_terminator, stdout);
+ putchar(line_terminator);
+ }
+ else {
+ printf("%s%06o %s %d\t",
+ tag,
+ ntohl(ce->ce_mode),
+ sha1_to_hex(ce->sha1),
+ ce_stage(ce));
+ write_name_quoted("", ce->name + offset, line_terminator, stdout);
+ putchar(line_terminator);
+ }
+}
+
+static void show_files(void)
+{
+ int i;
+
+ /* For cached/deleted files we don't need to even do the readdir */
+ if (show_others || show_killed) {
+ const char *path = ".", *base = "";
+ int baselen = prefix_len;
+
+ if (baselen)
+ path = base = prefix;
+ read_directory(path, base, baselen);
+ qsort(dir, nr_dir, sizeof(struct nond_on_fs *), cmp_name);
+ if (show_others)
+ show_other_files();
+ if (show_killed)
+ show_killed_files();
+ }
+ if (show_cached | show_stage) {
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (excluded(ce->name) != show_ignored)
+ continue;
+ if (show_unmerged && !ce_stage(ce))
+ continue;
+ show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
+ }
+ }
+ if (show_deleted | show_modified) {
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ struct stat st;
+ int err;
+ if (excluded(ce->name) != show_ignored)
+ continue;
+ err = lstat(ce->name, &st);
+ if (show_deleted && err)
+ show_ce_entry(tag_removed, ce);
+ if (show_modified && ce_modified(ce, &st))
+ show_ce_entry(tag_modified, ce);
+ }
+ }
+}
+
+/*
+ * Prune the index to only contain stuff starting with "prefix"
+ */
+static void prune_cache(void)
+{
+ int pos = cache_name_pos(prefix, prefix_len);
+ unsigned int first, last;
+
+ if (pos < 0)
+ pos = -pos-1;
+ active_cache += pos;
+ active_nr -= pos;
+ first = 0;
+ last = active_nr;
+ while (last > first) {
+ int next = (last + first) >> 1;
+ struct cache_entry *ce = active_cache[next];
+ if (!strncmp(ce->name, prefix, prefix_len)) {
+ first = next+1;
+ continue;
+ }
+ last = next;
+ }
+ active_nr = last;
+}
+
+static void verify_pathspec(void)
+{
+ const char **p, *n, *prev;
+ char *real_prefix;
+ unsigned long max;
+
+ prev = NULL;
+ max = PATH_MAX;
+ for (p = pathspec; (n = *p) != NULL; p++) {
+ int i, len = 0;
+ for (i = 0; i < max; i++) {
+ char c = n[i];
+ if (prev && prev[i] != c)
+ break;
+ if (!c || c == '*' || c == '?')
+ break;
+ if (c == '/')
+ len = i+1;
+ }
+ prev = n;
+ if (len < max) {
+ max = len;
+ if (!max)
+ break;
+ }
+ }
+
+ if (prefix_offset > max || memcmp(prev, prefix, prefix_offset))
+ die("git-ls-files: cannot generate relative filenames containing '..'");
+
+ real_prefix = NULL;
+ prefix_len = max;
+ if (max) {
+ real_prefix = xmalloc(max + 1);
+ memcpy(real_prefix, prev, max);
+ real_prefix[max] = 0;
+ }
+ prefix = real_prefix;
+}
+
+static const char ls_files_usage[] =
+ "git-ls-files [-z] [-t] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
+ "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
+ "[ --exclude-per-directory=<filename> ] [--] [<file>]*";
+
+int main(int argc, const char **argv)
+{
+ int i;
+ int exc_given = 0;
+
+ prefix = setup_git_directory();
+ if (prefix)
+ prefix_offset = strlen(prefix);
+ git_config(git_default_config);
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(arg, "-z")) {
+ line_terminator = 0;
+ continue;
+ }
+ if (!strcmp(arg, "-t")) {
+ tag_cached = "H ";
+ tag_unmerged = "M ";
+ tag_removed = "R ";
+ tag_modified = "C ";
+ tag_other = "? ";
+ tag_killed = "K ";
+ continue;
+ }
+ if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
+ show_cached = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
+ show_deleted = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
+ show_modified = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
+ show_others = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
+ show_ignored = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
+ show_stage = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
+ show_killed = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
+ /* There's no point in showing unmerged unless
+ * you also show the stage information.
+ */
+ show_stage = 1;
+ show_unmerged = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-x") && i+1 < argc) {
+ exc_given = 1;
+ add_exclude(argv[++i], "", 0, &exclude_list[EXC_CMDL]);
+ continue;
+ }
+ if (!strncmp(arg, "--exclude=", 10)) {
+ exc_given = 1;
+ add_exclude(arg+10, "", 0, &exclude_list[EXC_CMDL]);
+ continue;
+ }
+ if (!strcmp(arg, "-X") && i+1 < argc) {
+ exc_given = 1;
+ add_excludes_from_file(argv[++i]);
+ continue;
+ }
+ if (!strncmp(arg, "--exclude-from=", 15)) {
+ exc_given = 1;
+ add_excludes_from_file(arg+15);
+ continue;
+ }
+ if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+ exc_given = 1;
+ exclude_per_dir = arg + 24;
+ continue;
+ }
+ if (!strcmp(arg, "--full-name")) {
+ prefix_offset = 0;
+ continue;
+ }
+ if (*arg == '-')
+ usage(ls_files_usage);
+ break;
+ }
+
+ pathspec = get_pathspec(prefix, argv + i);
+
+ /* Verify that the pathspec matches the prefix */
+ if (pathspec)
+ verify_pathspec();
+
+ if (show_ignored && !exc_given) {
+ fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
+ argv[0]);
+ exit(1);
+ }
+
+ /* With no flags, we default to showing the cached files */
+ if (!(show_stage | show_deleted | show_others | show_unmerged |
+ show_killed | show_modified))
+ show_cached = 1;
+
+ read_cache();
+ if (prefix)
+ prune_cache();
+ show_files();
+ return 0;
+}
diff --git a/ls-tree.c b/ls-tree.c
new file mode 100644
index 000000000..d9f15e349
--- /dev/null
+++ b/ls-tree.c
@@ -0,0 +1,252 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "blob.h"
+#include "tree.h"
+#include "quote.h"
+
+static int line_termination = '\n';
+#define LS_RECURSIVE 1
+#define LS_TREE_ONLY 2
+static int ls_options = 0;
+
+static struct tree_entry_list root_entry;
+
+static void prepare_root(unsigned char *sha1)
+{
+ unsigned char rsha[20];
+ unsigned long size;
+ void *buf;
+ struct tree *root_tree;
+
+ buf = read_object_with_reference(sha1, "tree", &size, rsha);
+ free(buf);
+ if (!buf)
+ die("Could not read %s", sha1_to_hex(sha1));
+
+ root_tree = lookup_tree(rsha);
+ if (!root_tree)
+ die("Could not read %s", sha1_to_hex(sha1));
+
+ /* Prepare a fake entry */
+ root_entry.directory = 1;
+ root_entry.executable = root_entry.symlink = 0;
+ root_entry.mode = S_IFDIR;
+ root_entry.name = "";
+ root_entry.item.tree = root_tree;
+ root_entry.parent = NULL;
+}
+
+static int prepare_children(struct tree_entry_list *elem)
+{
+ if (!elem->directory)
+ return -1;
+ if (!elem->item.tree->object.parsed) {
+ struct tree_entry_list *e;
+ if (parse_tree(elem->item.tree))
+ return -1;
+ /* Set up the parent link */
+ for (e = elem->item.tree->entries; e; e = e->next)
+ e->parent = elem;
+ }
+ return 0;
+}
+
+static struct tree_entry_list *find_entry(const char *path, char *pathbuf)
+{
+ const char *next, *slash;
+ int len;
+ struct tree_entry_list *elem = &root_entry, *oldelem = NULL;
+
+ *(pathbuf) = '\0';
+
+ /* Find tree element, descending from root, that
+ * corresponds to the named path, lazily expanding
+ * the tree if possible.
+ */
+
+ while (path) {
+ /* The fact we still have path means that the caller
+ * wants us to make sure that elem at this point is a
+ * directory, and possibly descend into it. Even what
+ * is left is just trailing slashes, we loop back to
+ * here, and this call to prepare_children() will
+ * catch elem not being a tree. Nice.
+ */
+ if (prepare_children(elem))
+ return NULL;
+
+ slash = strchr(path, '/');
+ if (!slash) {
+ len = strlen(path);
+ next = NULL;
+ }
+ else {
+ next = slash + 1;
+ len = slash - path;
+ }
+ if (len) {
+ if (oldelem) {
+ pathbuf += sprintf(pathbuf, "%s/", oldelem->name);
+ }
+
+ /* (len == 0) if the original path was "drivers/char/"
+ * and we have run already two rounds, having elem
+ * pointing at the drivers/char directory.
+ */
+ elem = elem->item.tree->entries;
+ while (elem) {
+ if ((strlen(elem->name) == len) &&
+ !strncmp(elem->name, path, len)) {
+ /* found */
+ break;
+ }
+ elem = elem->next;
+ }
+ if (!elem)
+ return NULL;
+
+ oldelem = elem;
+ }
+ path = next;
+ }
+
+ return elem;
+}
+
+static const char *entry_type(struct tree_entry_list *e)
+{
+ return (e->directory ? "tree" : "blob");
+}
+
+static const char *entry_hex(struct tree_entry_list *e)
+{
+ return sha1_to_hex(e->directory
+ ? e->item.tree->object.sha1
+ : e->item.blob->object.sha1);
+}
+
+/* forward declaration for mutually recursive routines */
+static int show_entry(struct tree_entry_list *, int, char *pathbuf);
+
+static int show_children(struct tree_entry_list *e, int level, char *pathbuf)
+{
+ int oldlen = strlen(pathbuf);
+
+ if (e != &root_entry)
+ sprintf(pathbuf + oldlen, "%s/", e->name);
+
+ if (prepare_children(e))
+ die("internal error: ls-tree show_children called with non tree");
+ e = e->item.tree->entries;
+ while (e) {
+ show_entry(e, level, pathbuf);
+ e = e->next;
+ }
+
+ pathbuf[oldlen] = '\0';
+
+ return 0;
+}
+
+static int show_entry(struct tree_entry_list *e, int level, char *pathbuf)
+{
+ int err = 0;
+
+ if (e != &root_entry) {
+ printf("%06o %s %s ",
+ e->mode, entry_type(e), entry_hex(e));
+ write_name_quoted(pathbuf, e->name, line_termination, stdout);
+ putchar(line_termination);
+ }
+
+ if (e->directory) {
+ /* If this is a directory, we have the following cases:
+ * (1) This is the top-level request (explicit path from the
+ * command line, or "root" if there is no command line).
+ * a. Without any flag. We show direct children. We do not
+ * recurse into them.
+ * b. With -r. We do recurse into children.
+ * c. With -d. We do not recurse into children.
+ * (2) We came here because our caller is either (1-a) or
+ * (1-b).
+ * a. Without any flag. We do not show our children (which
+ * are grandchildren for the original request).
+ * b. With -r. We continue to recurse into our children.
+ * c. With -d. We should not have come here to begin with.
+ */
+ if (level == 0 && !(ls_options & LS_TREE_ONLY))
+ /* case (1)-a and (1)-b */
+ err = err | show_children(e, level+1, pathbuf);
+ else if (level && ls_options & LS_RECURSIVE)
+ /* case (2)-b */
+ err = err | show_children(e, level+1, pathbuf);
+ }
+ return err;
+}
+
+static int list_one(const char *path)
+{
+ int err = 0;
+ char pathbuf[MAXPATHLEN + 1];
+ struct tree_entry_list *e = find_entry(path, pathbuf);
+ if (!e) {
+ /* traditionally ls-tree does not complain about
+ * missing path. We may change this later to match
+ * what "/bin/ls -a" does, which is to complain.
+ */
+ return err;
+ }
+ err = err | show_entry(e, 0, pathbuf);
+ return err;
+}
+
+static int list(char **path)
+{
+ int i;
+ int err = 0;
+ for (i = 0; path[i]; i++)
+ err = err | list_one(path[i]);
+ return err;
+}
+
+static const char ls_tree_usage[] =
+ "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
+
+int main(int argc, char **argv)
+{
+ static char *path0[] = { "", NULL };
+ char **path;
+ unsigned char sha1[20];
+
+ while (1 < argc && argv[1][0] == '-') {
+ switch (argv[1][1]) {
+ case 'z':
+ line_termination = 0;
+ break;
+ case 'r':
+ ls_options |= LS_RECURSIVE;
+ break;
+ case 'd':
+ ls_options |= LS_TREE_ONLY;
+ break;
+ default:
+ usage(ls_tree_usage);
+ }
+ argc--; argv++;
+ }
+
+ if (argc < 2)
+ usage(ls_tree_usage);
+ if (get_sha1(argv[1], sha1) < 0)
+ usage(ls_tree_usage);
+
+ path = (argc == 2) ? path0 : (argv + 2);
+ prepare_root(sha1);
+ if (list(path) < 0)
+ die("list failed");
+ return 0;
+}
diff --git a/mailinfo.c b/mailinfo.c
new file mode 100644
index 000000000..cb853df99
--- /dev/null
+++ b/mailinfo.c
@@ -0,0 +1,753 @@
+/*
+ * Another stupid program, this one parsing the headers of an
+ * email to figure out authorship and subject
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <iconv.h>
+
+#ifdef NO_STRCASESTR
+extern char *gitstrcasestr(const char *haystack, const char *needle);
+#endif
+
+static FILE *cmitmsg, *patchfile;
+
+static int keep_subject = 0;
+static int metainfo_utf8 = 0;
+static char line[1000];
+static char date[1000];
+static char name[1000];
+static char email[1000];
+static char subject[1000];
+
+static enum {
+ TE_DONTCARE, TE_QP, TE_BASE64,
+} transfer_encoding;
+static char charset[256];
+
+static char multipart_boundary[1000];
+static int multipart_boundary_len;
+static int patch_lines = 0;
+
+static char *sanity_check(char *name, char *email)
+{
+ int len = strlen(name);
+ if (len < 3 || len > 60)
+ return email;
+ if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>'))
+ return email;
+ return name;
+}
+
+static int handle_from(char *line)
+{
+ char *at = strchr(line, '@');
+ char *dst;
+
+ if (!at)
+ return 0;
+
+ /*
+ * If we already have one email, don't take any confusing lines
+ */
+ if (*email && strchr(at+1, '@'))
+ return 0;
+
+ /* Pick up the string around '@', possibly delimited with <>
+ * pair; that is the email part. White them out while copying.
+ */
+ while (at > line) {
+ char c = at[-1];
+ if (isspace(c))
+ break;
+ if (c == '<') {
+ at[-1] = ' ';
+ break;
+ }
+ at--;
+ }
+ dst = email;
+ for (;;) {
+ unsigned char c = *at;
+ if (!c || c == '>' || isspace(c)) {
+ if (c == '>')
+ *at = ' ';
+ break;
+ }
+ *at++ = ' ';
+ *dst++ = c;
+ }
+ *dst++ = 0;
+
+ /* The remainder is name. It could be "John Doe <john.doe@xz>"
+ * or "john.doe@xz (John Doe)", but we have whited out the
+ * email part, so trim from both ends, possibly removing
+ * the () pair at the end.
+ */
+ at = line + strlen(line);
+ while (at > line) {
+ unsigned char c = *--at;
+ if (!isspace(c)) {
+ at[(c == ')') ? 0 : 1] = 0;
+ break;
+ }
+ }
+
+ at = line;
+ for (;;) {
+ unsigned char c = *at;
+ if (!c || !isspace(c)) {
+ if (c == '(')
+ at++;
+ break;
+ }
+ at++;
+ }
+ at = sanity_check(at, email);
+ strcpy(name, at);
+ return 1;
+}
+
+static int handle_date(char *line)
+{
+ strcpy(date, line);
+ return 0;
+}
+
+static int handle_subject(char *line)
+{
+ strcpy(subject, line);
+ return 0;
+}
+
+/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists. For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, char *attr)
+{
+ char *ends, *ap = strcasestr(line, name);
+ size_t sz;
+
+ if (!ap) {
+ *attr = 0;
+ return 0;
+ }
+ ap += strlen(name);
+ if (*ap == '"') {
+ ap++;
+ ends = "\"";
+ }
+ else
+ ends = "; \t";
+ sz = strcspn(ap, ends);
+ memcpy(attr, ap, sz);
+ attr[sz] = 0;
+ return 1;
+}
+
+static int handle_subcontent_type(char *line)
+{
+ /* We do not want to mess with boundary. Note that we do not
+ * handle nested multipart.
+ */
+ if (strcasestr(line, "boundary=")) {
+ fprintf(stderr, "Not handling nested multipart message.\n");
+ exit(1);
+ }
+ slurp_attr(line, "charset=", charset);
+ if (*charset) {
+ int i, c;
+ for (i = 0; (c = charset[i]) != 0; i++)
+ charset[i] = tolower(c);
+ }
+ return 0;
+}
+
+static int handle_content_type(char *line)
+{
+ *multipart_boundary = 0;
+ if (slurp_attr(line, "boundary=", multipart_boundary + 2)) {
+ memcpy(multipart_boundary, "--", 2);
+ multipart_boundary_len = strlen(multipart_boundary);
+ }
+ slurp_attr(line, "charset=", charset);
+ return 0;
+}
+
+static int handle_content_transfer_encoding(char *line)
+{
+ if (strcasestr(line, "base64"))
+ transfer_encoding = TE_BASE64;
+ else if (strcasestr(line, "quoted-printable"))
+ transfer_encoding = TE_QP;
+ else
+ transfer_encoding = TE_DONTCARE;
+ return 0;
+}
+
+static int is_multipart_boundary(const char *line)
+{
+ return (!memcmp(line, multipart_boundary, multipart_boundary_len));
+}
+
+static int eatspace(char *line)
+{
+ int len = strlen(line);
+ while (len > 0 && isspace(line[len-1]))
+ line[--len] = 0;
+ return len;
+}
+
+#define SEEN_FROM 01
+#define SEEN_DATE 02
+#define SEEN_SUBJECT 04
+
+/* First lines of body can have From:, Date:, and Subject: */
+static int handle_inbody_header(int *seen, char *line)
+{
+ if (!memcmp("From:", line, 5) && isspace(line[5])) {
+ if (!(*seen & SEEN_FROM) && handle_from(line+6)) {
+ *seen |= SEEN_FROM;
+ return 1;
+ }
+ }
+ if (!memcmp("Date:", line, 5) && isspace(line[5])) {
+ if (!(*seen & SEEN_DATE)) {
+ handle_date(line+6);
+ *seen |= SEEN_DATE;
+ return 1;
+ }
+ }
+ if (!memcmp("Subject:", line, 8) && isspace(line[8])) {
+ if (!(*seen & SEEN_SUBJECT)) {
+ handle_subject(line+9);
+ *seen |= SEEN_SUBJECT;
+ return 1;
+ }
+ }
+ if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+ if (!(*seen & SEEN_SUBJECT)) {
+ handle_subject(line);
+ *seen |= SEEN_SUBJECT;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static char *cleanup_subject(char *subject)
+{
+ if (keep_subject)
+ return subject;
+ for (;;) {
+ char *p;
+ int len, remove;
+ switch (*subject) {
+ case 'r': case 'R':
+ if (!memcmp("e:", subject+1, 2)) {
+ subject +=3;
+ continue;
+ }
+ break;
+ case ' ': case '\t': case ':':
+ subject++;
+ continue;
+
+ case '[':
+ p = strchr(subject, ']');
+ if (!p) {
+ subject++;
+ continue;
+ }
+ len = strlen(p);
+ remove = p - subject;
+ if (remove <= len *2) {
+ subject = p+1;
+ continue;
+ }
+ break;
+ }
+ return subject;
+ }
+}
+
+static void cleanup_space(char *buf)
+{
+ unsigned char c;
+ while ((c = *buf) != 0) {
+ buf++;
+ if (isspace(c)) {
+ buf[-1] = ' ';
+ c = *buf;
+ while (isspace(c)) {
+ int len = strlen(buf);
+ memmove(buf, buf+1, len);
+ c = *buf;
+ }
+ }
+ }
+}
+
+typedef int (*header_fn_t)(char *);
+struct header_def {
+ const char *name;
+ header_fn_t func;
+ int namelen;
+};
+
+static void check_header(char *line, int len, struct header_def *header)
+{
+ int i;
+
+ if (header[0].namelen <= 0) {
+ for (i = 0; header[i].name; i++)
+ header[i].namelen = strlen(header[i].name);
+ }
+ for (i = 0; header[i].name; i++) {
+ int len = header[i].namelen;
+ if (!strncasecmp(line, header[i].name, len) &&
+ line[len] == ':' && isspace(line[len + 1])) {
+ header[i].func(line + len + 2);
+ break;
+ }
+ }
+}
+
+static void check_subheader_line(char *line, int len)
+{
+ static struct header_def header[] = {
+ { "Content-Type", handle_subcontent_type },
+ { "Content-Transfer-Encoding",
+ handle_content_transfer_encoding },
+ { NULL },
+ };
+ check_header(line, len, header);
+}
+static void check_header_line(char *line, int len)
+{
+ static struct header_def header[] = {
+ { "From", handle_from },
+ { "Date", handle_date },
+ { "Subject", handle_subject },
+ { "Content-Type", handle_content_type },
+ { "Content-Transfer-Encoding",
+ handle_content_transfer_encoding },
+ { NULL },
+ };
+ check_header(line, len, header);
+}
+
+static int read_one_header_line(char *line, int sz, FILE *in)
+{
+ int ofs = 0;
+ while (ofs < sz) {
+ int peek, len;
+ if (fgets(line + ofs, sz - ofs, in) == NULL)
+ return ofs;
+ len = eatspace(line + ofs);
+ if (len == 0)
+ return ofs;
+ peek = fgetc(in); ungetc(peek, in);
+ if (peek == ' ' || peek == '\t') {
+ /* Yuck, 2822 header "folding" */
+ ofs += len;
+ continue;
+ }
+ return ofs + len;
+ }
+ return ofs;
+}
+
+static unsigned hexval(int c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return ~0;
+}
+
+static int decode_q_segment(char *in, char *ot, char *ep)
+{
+ int c;
+ while ((c = *in++) != 0 && (in <= ep)) {
+ if (c == '=') {
+ int d = *in++;
+ if (d == '\n' || !d)
+ break; /* drop trailing newline */
+ *ot++ = ((hexval(d) << 4) | hexval(*in++));
+ }
+ else
+ *ot++ = c;
+ }
+ *ot = 0;
+ return 0;
+}
+
+static int decode_b_segment(char *in, char *ot, char *ep)
+{
+ /* Decode in..ep, possibly in-place to ot */
+ int c, pos = 0, acc = 0;
+
+ while ((c = *in++) != 0 && (in <= ep)) {
+ if (c == '+')
+ c = 62;
+ else if (c == '/')
+ c = 63;
+ else if ('A' <= c && c <= 'Z')
+ c -= 'A';
+ else if ('a' <= c && c <= 'z')
+ c -= 'a' - 26;
+ else if ('0' <= c && c <= '9')
+ c -= '0' - 52;
+ else if (c == '=') {
+ /* padding is almost like (c == 0), except we do
+ * not output NUL resulting only from it;
+ * for now we just trust the data.
+ */
+ c = 0;
+ }
+ else
+ continue; /* garbage */
+ switch (pos++) {
+ case 0:
+ acc = (c << 2);
+ break;
+ case 1:
+ *ot++ = (acc | (c >> 4));
+ acc = (c & 15) << 4;
+ break;
+ case 2:
+ *ot++ = (acc | (c >> 2));
+ acc = (c & 3) << 6;
+ break;
+ case 3:
+ *ot++ = (acc | c);
+ acc = pos = 0;
+ break;
+ }
+ }
+ *ot = 0;
+ return 0;
+}
+
+static void convert_to_utf8(char *line, char *charset)
+{
+ if (*charset) {
+ char *in, *out;
+ size_t insize, outsize, nrc;
+ char outbuf[4096]; /* cheat */
+ iconv_t conv = iconv_open("utf-8", charset);
+
+ if (conv == (iconv_t) -1) {
+ fprintf(stderr, "cannot convert from %s to utf-8\n",
+ charset);
+ *charset = 0;
+ return;
+ }
+ in = line;
+ insize = strlen(in);
+ out = outbuf;
+ outsize = sizeof(outbuf);
+ nrc = iconv(conv, &in, &insize, &out, &outsize);
+ iconv_close(conv);
+ if (nrc == (size_t) -1)
+ return;
+ *out = 0;
+ strcpy(line, outbuf);
+ }
+}
+
+static void decode_header_bq(char *it)
+{
+ char *in, *out, *ep, *cp, *sp;
+ char outbuf[1000];
+
+ in = it;
+ out = outbuf;
+ while ((ep = strstr(in, "=?")) != NULL) {
+ int sz, encoding;
+ char charset_q[256], piecebuf[256];
+ if (in != ep) {
+ sz = ep - in;
+ memcpy(out, in, sz);
+ out += sz;
+ in += sz;
+ }
+ /* E.g.
+ * ep : "=?iso-2022-jp?B?GyR...?= foo"
+ * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+ */
+ ep += 2;
+ cp = strchr(ep, '?');
+ if (!cp)
+ return; /* no munging */
+ for (sp = ep; sp < cp; sp++)
+ charset_q[sp - ep] = tolower(*sp);
+ charset_q[cp - ep] = 0;
+ encoding = cp[1];
+ if (!encoding || cp[2] != '?')
+ return; /* no munging */
+ ep = strstr(cp + 3, "?=");
+ if (!ep)
+ return; /* no munging */
+ switch (tolower(encoding)) {
+ default:
+ return; /* no munging */
+ case 'b':
+ sz = decode_b_segment(cp + 3, piecebuf, ep);
+ break;
+ case 'q':
+ sz = decode_q_segment(cp + 3, piecebuf, ep);
+ break;
+ }
+ if (sz < 0)
+ return;
+ if (metainfo_utf8)
+ convert_to_utf8(piecebuf, charset_q);
+ strcpy(out, piecebuf);
+ out += strlen(out);
+ in = ep + 2;
+ }
+ strcpy(out, in);
+ strcpy(it, outbuf);
+}
+
+static void decode_transfer_encoding(char *line)
+{
+ char *ep;
+
+ switch (transfer_encoding) {
+ case TE_QP:
+ ep = line + strlen(line);
+ decode_q_segment(line, line, ep);
+ break;
+ case TE_BASE64:
+ ep = line + strlen(line);
+ decode_b_segment(line, line, ep);
+ break;
+ case TE_DONTCARE:
+ break;
+ }
+}
+
+static void handle_info(void)
+{
+ char *sub;
+ static int done_info = 0;
+
+ if (done_info)
+ return;
+
+ done_info = 1;
+ sub = cleanup_subject(subject);
+ cleanup_space(name);
+ cleanup_space(date);
+ cleanup_space(email);
+ cleanup_space(sub);
+
+ /* Unwrap inline B and Q encoding, and optionally
+ * normalize the meta information to utf8.
+ */
+ decode_header_bq(name);
+ decode_header_bq(date);
+ decode_header_bq(email);
+ decode_header_bq(sub);
+ printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n",
+ name, email, sub, date);
+}
+
+/* We are inside message body and have read line[] already.
+ * Spit out the commit log.
+ */
+static int handle_commit_msg(void)
+{
+ if (!cmitmsg)
+ return 0;
+ do {
+ if (!memcmp("diff -", line, 6) ||
+ !memcmp("---", line, 3) ||
+ !memcmp("Index: ", line, 7))
+ break;
+ if ((multipart_boundary[0] && is_multipart_boundary(line))) {
+ /* We come here when the first part had only
+ * the commit message without any patch. We
+ * pretend we have not seen this line yet, and
+ * go back to the loop.
+ */
+ return 1;
+ }
+
+ /* Unwrap transfer encoding and optionally
+ * normalize the log message to UTF-8.
+ */
+ decode_transfer_encoding(line);
+ if (metainfo_utf8)
+ convert_to_utf8(line, charset);
+ fputs(line, cmitmsg);
+ } while (fgets(line, sizeof(line), stdin) != NULL);
+ fclose(cmitmsg);
+ cmitmsg = NULL;
+ return 0;
+}
+
+/* We have done the commit message and have the first
+ * line of the patch in line[].
+ */
+static void handle_patch(void)
+{
+ do {
+ if (multipart_boundary[0] && is_multipart_boundary(line))
+ break;
+ /* Only unwrap transfer encoding but otherwise do not
+ * do anything. We do *NOT* want UTF-8 conversion
+ * here; we are dealing with the user payload.
+ */
+ decode_transfer_encoding(line);
+ fputs(line, patchfile);
+ patch_lines++;
+ } while (fgets(line, sizeof(line), stdin) != NULL);
+}
+
+/* multipart boundary and transfer encoding are set up for us, and we
+ * are at the end of the sub header. do equivalent of handle_body up
+ * to the next boundary without closing patchfile --- we will expect
+ * that the first part to contain commit message and a patch, and
+ * handle other parts as pure patches.
+ */
+static int handle_multipart_one_part(void)
+{
+ int seen = 0;
+ int n = 0;
+ int len;
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ again:
+ len = eatspace(line);
+ n++;
+ if (!len)
+ continue;
+ if (is_multipart_boundary(line))
+ break;
+ if (0 <= seen && handle_inbody_header(&seen, line))
+ continue;
+ seen = -1; /* no more inbody headers */
+ line[len] = '\n';
+ handle_info();
+ if (handle_commit_msg())
+ goto again;
+ handle_patch();
+ break;
+ }
+ if (n == 0)
+ return -1;
+ return 0;
+}
+
+static void handle_multipart_body(void)
+{
+ int part_num = 0;
+
+ /* Skip up to the first boundary */
+ while (fgets(line, sizeof(line), stdin) != NULL)
+ if (is_multipart_boundary(line)) {
+ part_num = 1;
+ break;
+ }
+ if (!part_num)
+ return;
+ /* We are on boundary line. Start slurping the subhead. */
+ while (1) {
+ int len = read_one_header_line(line, sizeof(line), stdin);
+ if (!len) {
+ if (handle_multipart_one_part() < 0)
+ return;
+ }
+ else
+ check_subheader_line(line, len);
+ }
+ fclose(patchfile);
+ if (!patch_lines) {
+ fprintf(stderr, "No patch found\n");
+ exit(1);
+ }
+}
+
+/* Non multipart message */
+static void handle_body(void)
+{
+ int seen = 0;
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ int len = eatspace(line);
+ if (!len)
+ continue;
+ if (0 <= seen && handle_inbody_header(&seen, line))
+ continue;
+ seen = -1; /* no more inbody headers */
+ line[len] = '\n';
+ handle_info();
+ handle_commit_msg();
+ handle_patch();
+ break;
+ }
+ fclose(patchfile);
+ if (!patch_lines) {
+ fprintf(stderr, "No patch found\n");
+ exit(1);
+ }
+}
+
+static const char mailinfo_usage[] =
+ "git-mailinfo [-k] [-u] msg patch <mail >info";
+
+static void usage(void) {
+ fprintf(stderr, "%s\n", mailinfo_usage);
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ while (1 < argc && argv[1][0] == '-') {
+ if (!strcmp(argv[1], "-k"))
+ keep_subject = 1;
+ else if (!strcmp(argv[1], "-u"))
+ metainfo_utf8 = 1;
+ else
+ usage();
+ argc--; argv++;
+ }
+
+ if (argc != 3)
+ usage();
+ cmitmsg = fopen(argv[1], "w");
+ if (!cmitmsg) {
+ perror(argv[1]);
+ exit(1);
+ }
+ patchfile = fopen(argv[2], "w");
+ if (!patchfile) {
+ perror(argv[2]);
+ exit(1);
+ }
+ while (1) {
+ int len = read_one_header_line(line, sizeof(line), stdin);
+ if (!len) {
+ if (multipart_boundary[0])
+ handle_multipart_body();
+ else
+ handle_body();
+ break;
+ }
+ check_header_line(line, len);
+ }
+ return 0;
+}
diff --git a/mailsplit.c b/mailsplit.c
new file mode 100644
index 000000000..189f4ed72
--- /dev/null
+++ b/mailsplit.c
@@ -0,0 +1,157 @@
+/*
+ * Totally braindamaged mbox splitter program.
+ *
+ * It just splits a mbox into a list of files: "0001" "0002" ..
+ * so you can process them further from there.
+ */
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include "cache.h"
+
+static const char git_mailsplit_usage[] =
+"git-mailsplit [-d<prec>] [<mbox>] <directory>";
+
+static int is_from_line(const char *line, int len)
+{
+ const char *colon;
+
+ if (len < 20 || memcmp("From ", line, 5))
+ return 0;
+
+ colon = line + len - 2;
+ line += 5;
+ for (;;) {
+ if (colon < line)
+ return 0;
+ if (*--colon == ':')
+ break;
+ }
+
+ if (!isdigit(colon[-4]) ||
+ !isdigit(colon[-2]) ||
+ !isdigit(colon[-1]) ||
+ !isdigit(colon[ 1]) ||
+ !isdigit(colon[ 2]))
+ return 0;
+
+ /* year */
+ if (strtol(colon+3, NULL, 10) <= 90)
+ return 0;
+
+ /* Ok, close enough */
+ return 1;
+}
+
+/* Could be as small as 64, enough to hold a Unix "From " line. */
+static char buf[4096];
+
+/* Called with the first line (potentially partial)
+ * already in buf[] -- normally that should begin with
+ * the Unix "From " line. Write it into the specified
+ * file.
+ */
+static int split_one(FILE *mbox, const char *name)
+{
+ FILE *output = NULL;
+ int len = strlen(buf);
+ int fd;
+ int status = 0;
+
+ if (!is_from_line(buf, len))
+ goto corrupt;
+
+ fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (fd < 0)
+ die("cannot open output file %s", name);
+ output = fdopen(fd, "w");
+
+ /* Copy it out, while searching for a line that begins with
+ * "From " and having something that looks like a date format.
+ */
+ for (;;) {
+ int is_partial = (buf[len-1] != '\n');
+
+ if (fputs(buf, output) == EOF)
+ die("cannot write output");
+
+ if (fgets(buf, sizeof(buf), mbox) == NULL) {
+ if (feof(mbox)) {
+ status = 1;
+ break;
+ }
+ die("cannot read mbox");
+ }
+ len = strlen(buf);
+ if (!is_partial && is_from_line(buf, len))
+ break; /* done with one message */
+ }
+ fclose(output);
+ return status;
+
+ corrupt:
+ if (output)
+ fclose(output);
+ unlink(name);
+ fprintf(stderr, "corrupt mailbox\n");
+ exit(1);
+}
+
+int main(int argc, const char **argv)
+{
+ int i, nr, nr_prec = 4;
+ FILE *mbox = NULL;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (arg[0] != '-')
+ break;
+ /* do flags here */
+ if (!strncmp(arg, "-d", 2)) {
+ nr_prec = strtol(arg + 2, NULL, 10);
+ if (nr_prec < 3 || 10 <= nr_prec)
+ usage(git_mailsplit_usage);
+ continue;
+ }
+ }
+
+ /* Either one remaining arg (dir), or two (mbox and dir) */
+ switch (argc - i) {
+ case 1:
+ mbox = stdin;
+ break;
+ case 2:
+ if ((mbox = fopen(argv[i], "r")) == NULL)
+ die("cannot open mbox %s for reading", argv[i]);
+ break;
+ default:
+ usage(git_mailsplit_usage);
+ }
+ if (chdir(argv[argc - 1]) < 0)
+ usage(git_mailsplit_usage);
+
+ nr = 0;
+ if (fgets(buf, sizeof(buf), mbox) == NULL)
+ die("cannot read mbox");
+
+ for (;;) {
+ char name[10];
+
+ sprintf(name, "%0*d", nr_prec, ++nr);
+ switch (split_one(mbox, name)) {
+ case 0:
+ break;
+ case 1:
+ printf("%d\n", nr);
+ return 0;
+ default:
+ exit(1);
+ }
+ }
+}
diff --git a/merge-base.c b/merge-base.c
new file mode 100644
index 000000000..751c3c281
--- /dev/null
+++ b/merge-base.c
@@ -0,0 +1,256 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+
+#define PARENT1 1
+#define PARENT2 2
+#define UNINTERESTING 4
+
+static struct commit *interesting(struct commit_list *list)
+{
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (commit->object.flags & UNINTERESTING)
+ continue;
+ return commit;
+ }
+ return NULL;
+}
+
+/*
+ * A pathological example of how this thing works.
+ *
+ * Suppose we had this commit graph, where chronologically
+ * the timestamp on the commit are A <= B <= C <= D <= E <= F
+ * and we are trying to figure out the merge base for E and F
+ * commits.
+ *
+ * F
+ * / \
+ * E A D
+ * \ / /
+ * B /
+ * \ /
+ * C
+ *
+ * First we push E and F to list to be processed. E gets bit 1
+ * and F gets bit 2. The list becomes:
+ *
+ * list=F(2) E(1), result=empty
+ *
+ * Then we pop F, the newest commit, from the list. Its flag is 2.
+ * We scan its parents, mark them reachable from the side that F is
+ * reachable from, and push them to the list:
+ *
+ * list=E(1) D(2) A(2), result=empty
+ *
+ * Next pop E and do the same.
+ *
+ * list=D(2) B(1) A(2), result=empty
+ *
+ * Next pop D and do the same.
+ *
+ * list=C(2) B(1) A(2), result=empty
+ *
+ * Next pop C and do the same.
+ *
+ * list=B(1) A(2), result=empty
+ *
+ * Now it is B's turn. We mark its parent, C, reachable from B's side,
+ * and push it to the list:
+ *
+ * list=C(3) A(2), result=empty
+ *
+ * Now pop C and notice it has flags==3. It is placed on the result list,
+ * and the list now contains:
+ *
+ * list=A(2), result=C(3)
+ *
+ * We pop A and do the same.
+ *
+ * list=B(3), result=C(3)
+ *
+ * Next, we pop B and something very interesting happens. It has flags==3
+ * so it is also placed on the result list, and its parents are marked
+ * uninteresting, retroactively, and placed back on the list:
+ *
+ * list=C(7), result=C(7) B(3)
+ *
+ * Now, list does not have any interesting commit. So we find the newest
+ * commit from the result list that is not marked uninteresting. Which is
+ * commit B.
+ *
+ *
+ * Another pathological example how this thing can fail to mark an ancestor
+ * of a merge base as UNINTERESTING without the postprocessing phase.
+ *
+ * 2
+ * H
+ * 1 / \
+ * G A \
+ * |\ / \
+ * | B \
+ * | \ \
+ * \ C F
+ * \ \ /
+ * \ D /
+ * \ | /
+ * \| /
+ * E
+ *
+ * list A B C D E F G H
+ * G1 H2 - - - - - - 1 2
+ * H2 E1 B1 - 1 - - 1 - 1 2
+ * F2 E1 B1 A2 2 1 - - 1 2 1 2
+ * E3 B1 A2 2 1 - - 3 2 1 2
+ * B1 A2 2 1 - - 3 2 1 2
+ * C1 A2 2 1 1 - 3 2 1 2
+ * D1 A2 2 1 1 1 3 2 1 2
+ * A2 2 1 1 1 3 2 1 2
+ * B3 2 3 1 1 3 2 1 2
+ * C7 2 3 7 1 3 2 1 2
+ *
+ * At this point, unfortunately, everybody in the list is
+ * uninteresting, so we fail to complete the following two
+ * steps to fully marking uninteresting commits.
+ *
+ * D7 2 3 7 7 3 2 1 2
+ * E7 2 3 7 7 7 2 1 2
+ *
+ * and we end up showing E as an interesting merge base.
+ */
+
+static int show_all = 0;
+
+static void mark_reachable_commits(struct commit_list *result,
+ struct commit_list *list)
+{
+ struct commit_list *tmp;
+
+ /*
+ * Postprocess to fully contaminate the well.
+ */
+ for (tmp = result; tmp; tmp = tmp->next) {
+ struct commit *c = tmp->item;
+ /* Reinject uninteresting ones to list,
+ * so we can scan their parents.
+ */
+ if (c->object.flags & UNINTERESTING)
+ commit_list_insert(c, &list);
+ }
+ while (list) {
+ struct commit *c = list->item;
+ struct commit_list *parents;
+
+ tmp = list;
+ list = list->next;
+ free(tmp);
+
+ /* Anything taken out of the list is uninteresting, so
+ * mark all its parents uninteresting. We do not
+ * parse new ones (we already parsed all the relevant
+ * ones).
+ */
+ parents = c->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+ if (!(p->object.flags & UNINTERESTING)) {
+ p->object.flags |= UNINTERESTING;
+ commit_list_insert(p, &list);
+ }
+ }
+ }
+}
+
+static int merge_base(struct commit *rev1, struct commit *rev2)
+{
+ struct commit_list *list = NULL;
+ struct commit_list *result = NULL;
+ struct commit_list *tmp = NULL;
+
+ if (rev1 == rev2) {
+ printf("%s\n", sha1_to_hex(rev1->object.sha1));
+ return 0;
+ }
+
+ parse_commit(rev1);
+ parse_commit(rev2);
+
+ rev1->object.flags |= 1;
+ rev2->object.flags |= 2;
+ insert_by_date(rev1, &list);
+ insert_by_date(rev2, &list);
+
+ while (interesting(list)) {
+ struct commit *commit = list->item;
+ struct commit_list *parents;
+ int flags = commit->object.flags & 7;
+
+ tmp = list;
+ list = list->next;
+ free(tmp);
+ if (flags == 3) {
+ insert_by_date(commit, &result);
+
+ /* Mark parents of a found merge uninteresting */
+ flags |= UNINTERESTING;
+ }
+ parents = commit->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+ if ((p->object.flags & flags) == flags)
+ continue;
+ parse_commit(p);
+ p->object.flags |= flags;
+ insert_by_date(p, &list);
+ }
+ }
+
+ if (!result)
+ return 1;
+
+ if (result->next && list)
+ mark_reachable_commits(result, list);
+
+ while (result) {
+ struct commit *commit = result->item;
+ result = result->next;
+ if (commit->object.flags & UNINTERESTING)
+ continue;
+ printf("%s\n", sha1_to_hex(commit->object.sha1));
+ if (!show_all)
+ return 0;
+ commit->object.flags |= UNINTERESTING;
+ }
+ return 0;
+}
+
+static const char merge_base_usage[] =
+"git-merge-base [--all] <commit-id> <commit-id>";
+
+int main(int argc, char **argv)
+{
+ struct commit *rev1, *rev2;
+ unsigned char rev1key[20], rev2key[20];
+
+ while (1 < argc && argv[1][0] == '-') {
+ char *arg = argv[1];
+ if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
+ show_all = 1;
+ else
+ usage(merge_base_usage);
+ argc--; argv++;
+ }
+ if (argc != 3 ||
+ get_sha1(argv[1], rev1key) ||
+ get_sha1(argv[2], rev2key))
+ usage(merge_base_usage);
+ rev1 = lookup_commit_reference(rev1key);
+ rev2 = lookup_commit_reference(rev2key);
+ if (!rev1 || !rev2)
+ return 1;
+ return merge_base(rev1, rev2);
+}
diff --git a/merge-index.c b/merge-index.c
new file mode 100644
index 000000000..727527fd5
--- /dev/null
+++ b/merge-index.c
@@ -0,0 +1,135 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include "cache.h"
+
+static const char *pgm = NULL;
+static const char *arguments[8];
+static int one_shot, quiet;
+static int err;
+
+static void run_program(void)
+{
+ int pid = fork(), status;
+
+ if (pid < 0)
+ die("unable to fork");
+ if (!pid) {
+ execlp(pgm, arguments[0],
+ arguments[1],
+ arguments[2],
+ arguments[3],
+ arguments[4],
+ arguments[5],
+ arguments[6],
+ arguments[7],
+ NULL);
+ die("unable to execute '%s'", pgm);
+ }
+ if (waitpid(pid, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status)) {
+ if (one_shot) {
+ err++;
+ } else {
+ if (!quiet)
+ die("merge program failed");
+ exit(1);
+ }
+ }
+}
+
+static int merge_entry(int pos, const char *path)
+{
+ int found;
+
+ if (pos >= active_nr)
+ die("git-merge-index: %s not in the cache", path);
+ arguments[0] = pgm;
+ arguments[1] = "";
+ arguments[2] = "";
+ arguments[3] = "";
+ arguments[4] = path;
+ arguments[5] = "";
+ arguments[6] = "";
+ arguments[7] = "";
+ found = 0;
+ do {
+ static char hexbuf[4][60];
+ static char ownbuf[4][60];
+ struct cache_entry *ce = active_cache[pos];
+ int stage = ce_stage(ce);
+
+ if (strcmp(ce->name, path))
+ break;
+ found++;
+ strcpy(hexbuf[stage], sha1_to_hex(ce->sha1));
+ sprintf(ownbuf[stage], "%o", ntohl(ce->ce_mode) & (~S_IFMT));
+ arguments[stage] = hexbuf[stage];
+ arguments[stage + 4] = ownbuf[stage];
+ } while (++pos < active_nr);
+ if (!found)
+ die("git-merge-index: %s not in the cache", path);
+ run_program();
+ return found;
+}
+
+static void merge_file(const char *path)
+{
+ int pos = cache_name_pos(path, strlen(path));
+
+ /*
+ * If it already exists in the cache as stage0, it's
+ * already merged and there is nothing to do.
+ */
+ if (pos < 0)
+ merge_entry(-pos-1, path);
+}
+
+static void merge_all(void)
+{
+ int i;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (!ce_stage(ce))
+ continue;
+ i += merge_entry(i, ce->name)-1;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int i, force_file = 0;
+
+ if (argc < 3)
+ usage("git-merge-index [-o] [-q] <merge-program> (-a | <filename>*)");
+
+ read_cache();
+
+ i = 1;
+ if (!strcmp(argv[i], "-o")) {
+ one_shot = 1;
+ i++;
+ }
+ if (!strcmp(argv[i], "-q")) {
+ quiet = 1;
+ i++;
+ }
+ pgm = argv[i++];
+ for (; i < argc; i++) {
+ char *arg = argv[i];
+ if (!force_file && *arg == '-') {
+ if (!strcmp(arg, "--")) {
+ force_file = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-a")) {
+ merge_all();
+ continue;
+ }
+ die("git-merge-index: unknown option %s", arg);
+ }
+ merge_file(arg);
+ }
+ if (err && !quiet)
+ die("merge program failed");
+ return err;
+}
diff --git a/mktag.c b/mktag.c
new file mode 100644
index 000000000..585677eb8
--- /dev/null
+++ b/mktag.c
@@ -0,0 +1,136 @@
+#include "cache.h"
+
+/*
+ * A signature file has a very simple fixed format: three lines
+ * of "object <sha1>" + "type <typename>" + "tag <tagname>",
+ * followed by some free-form signature that git itself doesn't
+ * care about, but that can be verified with gpg or similar.
+ *
+ * The first three lines are guaranteed to be at least 63 bytes:
+ * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
+ * shortest possible type-line, and "tag .\n" at 6 bytes is the
+ * shortest single-character-tag line.
+ *
+ * We also artificially limit the size of the full object to 8kB.
+ * Just because I'm a lazy bastard, and if you can't fit a signature
+ * in that size, you're doing something wrong.
+ */
+
+// Some random size
+#define MAXSIZE (8192)
+
+/*
+ * We refuse to tag something we can't verify. Just because.
+ */
+static int verify_object(unsigned char *sha1, const char *expected_type)
+{
+ int ret = -1;
+ char type[100];
+ unsigned long size;
+ void *buffer = read_sha1_file(sha1, type, &size);
+
+ if (buffer) {
+ if (!strcmp(type, expected_type))
+ ret = check_sha1_signature(sha1, buffer, size, type);
+ free(buffer);
+ }
+ return ret;
+}
+
+static int verify_tag(char *buffer, unsigned long size)
+{
+ int typelen;
+ char type[20];
+ unsigned char sha1[20];
+ const char *object, *type_line, *tag_line, *tagger_line;
+
+ if (size < 64 || size > MAXSIZE-1)
+ return -1;
+ buffer[size] = 0;
+
+ /* Verify object line */
+ object = buffer;
+ if (memcmp(object, "object ", 7))
+ return -1;
+ if (get_sha1_hex(object + 7, sha1))
+ return -1;
+
+ /* Verify type line */
+ type_line = object + 48;
+ if (memcmp(type_line - 1, "\ntype ", 6))
+ return -1;
+
+ /* Verify tag-line */
+ tag_line = strchr(type_line, '\n');
+ if (!tag_line)
+ return -1;
+ tag_line++;
+ if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n')
+ return -1;
+
+ /* Get the actual type */
+ typelen = tag_line - type_line - strlen("type \n");
+ if (typelen >= sizeof(type))
+ return -1;
+ memcpy(type, type_line+5, typelen);
+ type[typelen] = 0;
+
+ /* Verify that the object matches */
+ if (get_sha1_hex(object + 7, sha1))
+ return -1;
+ if (verify_object(sha1, type))
+ return -1;
+
+ /* Verify the tag-name: we don't allow control characters or spaces in it */
+ tag_line += 4;
+ for (;;) {
+ unsigned char c = *tag_line++;
+ if (c == '\n')
+ break;
+ if (c > ' ')
+ continue;
+ return -1;
+ }
+
+ /* Verify the tagger line */
+ tagger_line = tag_line;
+
+ if (memcmp(tagger_line, "tagger", 6) || (tagger_line[6] == '\n'))
+ return -1;
+
+ /* The actual stuff afterwards we don't care about.. */
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ unsigned long size;
+ char buffer[MAXSIZE];
+ unsigned char result_sha1[20];
+
+ if (argc != 1)
+ usage("cat <signaturefile> | git-mktag");
+
+ // Read the signature
+ size = 0;
+ for (;;) {
+ int ret = read(0, buffer + size, MAXSIZE - size);
+ if (!ret)
+ break;
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ continue;
+ break;
+ }
+ size += ret;
+ }
+
+ // Verify it for some basic sanity: it needs to start with "object <sha1>\ntype\ntagger "
+ if (verify_tag(buffer, size) < 0)
+ die("invalid tag signature file");
+
+ if (write_sha1_file(buffer, size, "tag", result_sha1) < 0)
+ die("unable to write tag file");
+ printf("%s\n", sha1_to_hex(result_sha1));
+ return 0;
+}
diff --git a/mozilla-sha1/sha1.c b/mozilla-sha1/sha1.c
new file mode 100644
index 000000000..847531d19
--- /dev/null
+++ b/mozilla-sha1/sha1.c
@@ -0,0 +1,152 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is SHA 180-1 Reference Implementation (Compact version)
+ *
+ * The Initial Developer of the Original Code is Paul Kocher of
+ * Cryptography Research. Portions created by Paul Kocher are
+ * Copyright (C) 1995-9 by Cryptography Research, Inc. All
+ * Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Paul Kocher
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable
+ * instead of those above. If you wish to allow use of your
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL. If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+#include "sha1.h"
+
+static void shaHashBlock(SHA_CTX *ctx);
+
+void SHA1_Init(SHA_CTX *ctx) {
+ int i;
+
+ ctx->lenW = 0;
+ ctx->sizeHi = ctx->sizeLo = 0;
+
+ /* Initialize H with the magic constants (see FIPS180 for constants)
+ */
+ ctx->H[0] = 0x67452301;
+ ctx->H[1] = 0xefcdab89;
+ ctx->H[2] = 0x98badcfe;
+ ctx->H[3] = 0x10325476;
+ ctx->H[4] = 0xc3d2e1f0;
+
+ for (i = 0; i < 80; i++)
+ ctx->W[i] = 0;
+}
+
+
+void SHA1_Update(SHA_CTX *ctx, const void *_dataIn, int len) {
+ const unsigned char *dataIn = _dataIn;
+ int i;
+
+ /* Read the data into W and process blocks as they get full
+ */
+ for (i = 0; i < len; i++) {
+ ctx->W[ctx->lenW / 4] <<= 8;
+ ctx->W[ctx->lenW / 4] |= (unsigned int)dataIn[i];
+ if ((++ctx->lenW) % 64 == 0) {
+ shaHashBlock(ctx);
+ ctx->lenW = 0;
+ }
+ ctx->sizeLo += 8;
+ ctx->sizeHi += (ctx->sizeLo < 8);
+ }
+}
+
+
+void SHA1_Final(unsigned char hashout[20], SHA_CTX *ctx) {
+ unsigned char pad0x80 = 0x80;
+ unsigned char pad0x00 = 0x00;
+ unsigned char padlen[8];
+ int i;
+
+ /* Pad with a binary 1 (e.g. 0x80), then zeroes, then length
+ */
+ padlen[0] = (unsigned char)((ctx->sizeHi >> 24) & 255);
+ padlen[1] = (unsigned char)((ctx->sizeHi >> 16) & 255);
+ padlen[2] = (unsigned char)((ctx->sizeHi >> 8) & 255);
+ padlen[3] = (unsigned char)((ctx->sizeHi >> 0) & 255);
+ padlen[4] = (unsigned char)((ctx->sizeLo >> 24) & 255);
+ padlen[5] = (unsigned char)((ctx->sizeLo >> 16) & 255);
+ padlen[6] = (unsigned char)((ctx->sizeLo >> 8) & 255);
+ padlen[7] = (unsigned char)((ctx->sizeLo >> 0) & 255);
+ SHA1_Update(ctx, &pad0x80, 1);
+ while (ctx->lenW != 56)
+ SHA1_Update(ctx, &pad0x00, 1);
+ SHA1_Update(ctx, padlen, 8);
+
+ /* Output hash
+ */
+ for (i = 0; i < 20; i++) {
+ hashout[i] = (unsigned char)(ctx->H[i / 4] >> 24);
+ ctx->H[i / 4] <<= 8;
+ }
+
+ /*
+ * Re-initialize the context (also zeroizes contents)
+ */
+ SHA1_Init(ctx);
+}
+
+
+#define SHA_ROT(X,n) (((X) << (n)) | ((X) >> (32-(n))))
+
+static void shaHashBlock(SHA_CTX *ctx) {
+ int t;
+ unsigned int A,B,C,D,E,TEMP;
+
+ for (t = 16; t <= 79; t++)
+ ctx->W[t] =
+ SHA_ROT(ctx->W[t-3] ^ ctx->W[t-8] ^ ctx->W[t-14] ^ ctx->W[t-16], 1);
+
+ A = ctx->H[0];
+ B = ctx->H[1];
+ C = ctx->H[2];
+ D = ctx->H[3];
+ E = ctx->H[4];
+
+ for (t = 0; t <= 19; t++) {
+ TEMP = SHA_ROT(A,5) + (((C^D)&B)^D) + E + ctx->W[t] + 0x5a827999;
+ E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+ }
+ for (t = 20; t <= 39; t++) {
+ TEMP = SHA_ROT(A,5) + (B^C^D) + E + ctx->W[t] + 0x6ed9eba1;
+ E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+ }
+ for (t = 40; t <= 59; t++) {
+ TEMP = SHA_ROT(A,5) + ((B&C)|(D&(B|C))) + E + ctx->W[t] + 0x8f1bbcdc;
+ E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+ }
+ for (t = 60; t <= 79; t++) {
+ TEMP = SHA_ROT(A,5) + (B^C^D) + E + ctx->W[t] + 0xca62c1d6;
+ E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP;
+ }
+
+ ctx->H[0] += A;
+ ctx->H[1] += B;
+ ctx->H[2] += C;
+ ctx->H[3] += D;
+ ctx->H[4] += E;
+}
+
diff --git a/mozilla-sha1/sha1.h b/mozilla-sha1/sha1.h
new file mode 100644
index 000000000..5d82afa3b
--- /dev/null
+++ b/mozilla-sha1/sha1.h
@@ -0,0 +1,45 @@
+/*
+ * The contents of this file are subject to the Mozilla Public
+ * License Version 1.1 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * The Original Code is SHA 180-1 Header File
+ *
+ * The Initial Developer of the Original Code is Paul Kocher of
+ * Cryptography Research. Portions created by Paul Kocher are
+ * Copyright (C) 1995-9 by Cryptography Research, Inc. All
+ * Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Paul Kocher
+ *
+ * Alternatively, the contents of this file may be used under the
+ * terms of the GNU General Public License Version 2 or later (the
+ * "GPL"), in which case the provisions of the GPL are applicable
+ * instead of those above. If you wish to allow use of your
+ * version of this file only under the terms of the GPL and not to
+ * allow others to use your version of this file under the MPL,
+ * indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by
+ * the GPL. If you do not delete the provisions above, a recipient
+ * may use your version of this file under either the MPL or the
+ * GPL.
+ */
+
+typedef struct {
+ unsigned int H[5];
+ unsigned int W[80];
+ int lenW;
+ unsigned int sizeHi,sizeLo;
+} SHA_CTX;
+
+void SHA1_Init(SHA_CTX *ctx);
+void SHA1_Update(SHA_CTX *ctx, const void *dataIn, int len);
+void SHA1_Final(unsigned char hashout[20], SHA_CTX *ctx);
diff --git a/name-rev.c b/name-rev.c
new file mode 100644
index 000000000..59194f134
--- /dev/null
+++ b/name-rev.c
@@ -0,0 +1,246 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+
+static const char name_rev_usage[] =
+ "git-name-rev [--tags] ( --all | --stdin | commitish [commitish...] )\n";
+
+typedef struct rev_name {
+ const char *tip_name;
+ int merge_traversals;
+ int generation;
+} rev_name;
+
+static long cutoff = LONG_MAX;
+
+static void name_rev(struct commit *commit,
+ const char *tip_name, int merge_traversals, int generation,
+ int deref)
+{
+ struct rev_name *name = (struct rev_name *)commit->object.util;
+ struct commit_list *parents;
+ int parent_number = 0;
+
+ if (!commit->object.parsed)
+ parse_commit(commit);
+
+ if (commit->date < cutoff)
+ return;
+
+ if (deref) {
+ char *new_name = xmalloc(strlen(tip_name)+3);
+ strcpy(new_name, tip_name);
+ strcat(new_name, "^0");
+ tip_name = new_name;
+
+ if (generation)
+ die("generation: %d, but deref?", generation);
+ }
+
+ if (name == NULL) {
+ name = xmalloc(sizeof(rev_name));
+ commit->object.util = name;
+ goto copy_data;
+ } else if (name->merge_traversals > merge_traversals ||
+ (name->merge_traversals == merge_traversals &&
+ name->generation > generation)) {
+copy_data:
+ name->tip_name = tip_name;
+ name->merge_traversals = merge_traversals;
+ name->generation = generation;
+ } else
+ return;
+
+ for (parents = commit->parents;
+ parents;
+ parents = parents->next, parent_number++) {
+ if (parent_number > 0) {
+ char *new_name = xmalloc(strlen(tip_name)+8);
+
+ if (generation > 0)
+ sprintf(new_name, "%s~%d^%d", tip_name,
+ generation, parent_number);
+ else
+ sprintf(new_name, "%s^%d", tip_name, parent_number);
+
+ name_rev(parents->item, new_name,
+ merge_traversals + 1 , 0, 0);
+ } else {
+ name_rev(parents->item, tip_name, merge_traversals,
+ generation + 1, 0);
+ }
+ }
+}
+
+static int tags_only = 0;
+
+static int name_ref(const char *path, const unsigned char *sha1)
+{
+ struct object *o = parse_object(sha1);
+ int deref = 0;
+
+ if (tags_only && strncmp(path, "refs/tags/", 10))
+ return 0;
+
+ while (o && o->type == tag_type) {
+ struct tag *t = (struct tag *) o;
+ if (!t->tagged)
+ break; /* broken repository */
+ o = parse_object(t->tagged->sha1);
+ deref = 1;
+ }
+ if (o && o->type == commit_type) {
+ struct commit *commit = (struct commit *)o;
+ const char *p;
+
+ while ((p = strchr(path, '/')))
+ path = p+1;
+
+ name_rev(commit, strdup(path), 0, 0, deref);
+ }
+ return 0;
+}
+
+/* returns a static buffer */
+static const char* get_rev_name(struct object *o)
+{
+ static char buffer[1024];
+ struct rev_name *n = (struct rev_name *)o->util;
+ if (!n)
+ return "undefined";
+
+ if (!n->generation)
+ return n->tip_name;
+
+ snprintf(buffer, sizeof(buffer), "%s~%d", n->tip_name, n->generation);
+
+ return buffer;
+}
+
+int main(int argc, char **argv)
+{
+ struct object_list *revs = NULL;
+ struct object_list **walker = &revs;
+ int as_is = 0, all = 0, transform_stdin = 0;
+
+ setup_git_directory();
+
+ if (argc < 2)
+ usage(name_rev_usage);
+
+ for (--argc, ++argv; argc; --argc, ++argv) {
+ unsigned char sha1[20];
+ struct object *o;
+ struct commit *commit;
+
+ if (!as_is && (*argv)[0] == '-') {
+ if (!strcmp(*argv, "--")) {
+ as_is = 1;
+ continue;
+ } else if (!strcmp(*argv, "--tags")) {
+ tags_only = 1;
+ continue;
+ } else if (!strcmp(*argv, "--all")) {
+ if (argc > 1)
+ die("Specify either a list, or --all, not both!");
+ all = 1;
+ cutoff = 0;
+ continue;
+ } else if (!strcmp(*argv, "--stdin")) {
+ if (argc > 1)
+ die("Specify either a list, or --stdin, not both!");
+ transform_stdin = 1;
+ cutoff = 0;
+ continue;
+ }
+ usage(name_rev_usage);
+ }
+
+ if (get_sha1(*argv, sha1)) {
+ fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
+ *argv);
+ continue;
+ }
+
+ o = deref_tag(parse_object(sha1), *argv, 0);
+ if (!o || o->type != commit_type) {
+ fprintf(stderr, "Could not get commit for %s. Skipping.\n",
+ *argv);
+ continue;
+ }
+
+ commit = (struct commit *)o;
+
+ if (cutoff > commit->date)
+ cutoff = commit->date;
+
+ object_list_append((struct object *)commit, walker);
+ (*walker)->name = *argv;
+ walker = &((*walker)->next);
+ }
+
+ for_each_ref(name_ref);
+
+ if (transform_stdin) {
+ char buffer[2048];
+ char *p, *p_start;
+
+ while (!feof(stdin)) {
+ int forty = 0;
+ p = fgets(buffer, sizeof(buffer), stdin);
+ if (!p)
+ break;
+
+ for (p_start = p; *p; p++) {
+#define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
+ if (!ishex(*p))
+ forty = 0;
+ else if (++forty == 40 &&
+ !ishex(*(p+1))) {
+ unsigned char sha1[40];
+ const char *name = "undefined";
+ char c = *(p+1);
+
+ forty = 0;
+
+ *(p+1) = 0;
+ if (!get_sha1(p - 39, sha1)) {
+ struct object *o =
+ lookup_object(sha1);
+ if (o)
+ name = get_rev_name(o);
+ }
+ *(p+1) = c;
+
+ if (!strcmp(name, "undefined"))
+ continue;
+
+ fwrite(p_start, p - p_start, 1, stdout);
+ fputc('(', stdout);
+ fputs(name, stdout);
+ fputc(')', stdout);
+ p_start = p + 1;
+ }
+ }
+
+ /* flush */
+ if (p_start != p)
+ fwrite(p_start, p - p_start, 1, stdout);
+ }
+ } else if (all) {
+ extern struct object **objs;
+ extern int nr_objs;
+ int i;
+
+ for (i = 0; i < nr_objs; i++)
+ printf("%s %s\n", sha1_to_hex(objs[i]->sha1),
+ get_rev_name(objs[i]));
+ } else
+ for ( ; revs; revs = revs->next)
+ printf("%s %s\n", revs->name, get_rev_name(revs->item));
+
+ return 0;
+}
+
diff --git a/object.c b/object.c
new file mode 100644
index 000000000..427e14cae
--- /dev/null
+++ b/object.c
@@ -0,0 +1,249 @@
+#include "object.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "cache.h"
+#include "tag.h"
+
+struct object **objs;
+int nr_objs;
+static int obj_allocs;
+
+int track_object_refs = 1;
+
+static int find_object(const unsigned char *sha1)
+{
+ int first = 0, last = nr_objs;
+
+ while (first < last) {
+ int next = (first + last) / 2;
+ struct object *obj = objs[next];
+ int cmp;
+
+ cmp = memcmp(sha1, obj->sha1, 20);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
+}
+
+struct object *lookup_object(const unsigned char *sha1)
+{
+ int pos = find_object(sha1);
+ if (pos >= 0)
+ return objs[pos];
+ return NULL;
+}
+
+void created_object(const unsigned char *sha1, struct object *obj)
+{
+ int pos = find_object(sha1);
+
+ obj->parsed = 0;
+ memcpy(obj->sha1, sha1, 20);
+ obj->type = NULL;
+ obj->refs = NULL;
+ obj->used = 0;
+
+ if (pos >= 0)
+ die("Inserting %s twice\n", sha1_to_hex(sha1));
+ pos = -pos-1;
+
+ if (obj_allocs == nr_objs) {
+ obj_allocs = alloc_nr(obj_allocs);
+ objs = xrealloc(objs, obj_allocs * sizeof(struct object *));
+ }
+
+ /* Insert it into the right place */
+ memmove(objs + pos + 1, objs + pos, (nr_objs - pos) *
+ sizeof(struct object *));
+
+ objs[pos] = obj;
+ nr_objs++;
+}
+
+struct object_refs *alloc_object_refs(unsigned count)
+{
+ struct object_refs *refs;
+ size_t size = sizeof(*refs) + count*sizeof(struct object *);
+
+ refs = xmalloc(size);
+ memset(refs, 0, size);
+ refs->count = count;
+ return refs;
+}
+
+static int compare_object_pointers(const void *a, const void *b)
+{
+ const struct object * const *pa = a;
+ const struct object * const *pb = b;
+ return *pa - *pb;
+}
+
+void set_object_refs(struct object *obj, struct object_refs *refs)
+{
+ unsigned int i, j;
+
+ /* Do not install empty list of references */
+ if (refs->count < 1) {
+ free(refs);
+ return;
+ }
+
+ /* Sort the list and filter out duplicates */
+ qsort(refs->ref, refs->count, sizeof(refs->ref[0]),
+ compare_object_pointers);
+ for (i = j = 1; i < refs->count; i++) {
+ if (refs->ref[i] != refs->ref[i - 1])
+ refs->ref[j++] = refs->ref[i];
+ }
+ if (j < refs->count) {
+ /* Duplicates were found - reallocate list */
+ size_t size = sizeof(*refs) + j*sizeof(struct object *);
+ refs->count = j;
+ refs = xrealloc(refs, size);
+ }
+
+ for (i = 0; i < refs->count; i++)
+ refs->ref[i]->used = 1;
+ obj->refs = refs;
+}
+
+void mark_reachable(struct object *obj, unsigned int mask)
+{
+ if (!track_object_refs)
+ die("cannot do reachability with object refs turned off");
+ /* If we've been here already, don't bother */
+ if (obj->flags & mask)
+ return;
+ obj->flags |= mask;
+ if (obj->refs) {
+ const struct object_refs *refs = obj->refs;
+ unsigned i;
+ for (i = 0; i < refs->count; i++)
+ mark_reachable(refs->ref[i], mask);
+ }
+}
+
+struct object *lookup_object_type(const unsigned char *sha1, const char *type)
+{
+ if (!type) {
+ return lookup_unknown_object(sha1);
+ } else if (!strcmp(type, blob_type)) {
+ return &lookup_blob(sha1)->object;
+ } else if (!strcmp(type, tree_type)) {
+ return &lookup_tree(sha1)->object;
+ } else if (!strcmp(type, commit_type)) {
+ return &lookup_commit(sha1)->object;
+ } else if (!strcmp(type, tag_type)) {
+ return &lookup_tag(sha1)->object;
+ } else {
+ error("Unknown type %s", type);
+ return NULL;
+ }
+}
+
+union any_object {
+ struct object object;
+ struct commit commit;
+ struct tree tree;
+ struct blob blob;
+ struct tag tag;
+};
+
+struct object *lookup_unknown_object(const unsigned char *sha1)
+{
+ struct object *obj = lookup_object(sha1);
+ if (!obj) {
+ union any_object *ret = xmalloc(sizeof(*ret));
+ memset(ret, 0, sizeof(*ret));
+ created_object(sha1, &ret->object);
+ ret->object.type = NULL;
+ return &ret->object;
+ }
+ return obj;
+}
+
+struct object *parse_object(const unsigned char *sha1)
+{
+ unsigned long size;
+ char type[20];
+ void *buffer = read_sha1_file(sha1, type, &size);
+ if (buffer) {
+ struct object *obj;
+ if (check_sha1_signature(sha1, buffer, size, type) < 0)
+ printf("sha1 mismatch %s\n", sha1_to_hex(sha1));
+ if (!strcmp(type, "blob")) {
+ struct blob *blob = lookup_blob(sha1);
+ parse_blob_buffer(blob, buffer, size);
+ obj = &blob->object;
+ } else if (!strcmp(type, "tree")) {
+ struct tree *tree = lookup_tree(sha1);
+ parse_tree_buffer(tree, buffer, size);
+ obj = &tree->object;
+ } else if (!strcmp(type, "commit")) {
+ struct commit *commit = lookup_commit(sha1);
+ parse_commit_buffer(commit, buffer, size);
+ if (!commit->buffer) {
+ commit->buffer = buffer;
+ buffer = NULL;
+ }
+ obj = &commit->object;
+ } else if (!strcmp(type, "tag")) {
+ struct tag *tag = lookup_tag(sha1);
+ parse_tag_buffer(tag, buffer, size);
+ obj = &tag->object;
+ } else {
+ obj = NULL;
+ }
+ free(buffer);
+ return obj;
+ }
+ return NULL;
+}
+
+struct object_list *object_list_insert(struct object *item,
+ struct object_list **list_p)
+{
+ struct object_list *new_list = xmalloc(sizeof(struct object_list));
+ new_list->item = item;
+ new_list->next = *list_p;
+ *list_p = new_list;
+ return new_list;
+}
+
+void object_list_append(struct object *item,
+ struct object_list **list_p)
+{
+ while (*list_p) {
+ list_p = &((*list_p)->next);
+ }
+ *list_p = xmalloc(sizeof(struct object_list));
+ (*list_p)->next = NULL;
+ (*list_p)->item = item;
+}
+
+unsigned object_list_length(struct object_list *list)
+{
+ unsigned ret = 0;
+ while (list) {
+ list = list->next;
+ ret++;
+ }
+ return ret;
+}
+
+int object_list_contains(struct object_list *list, struct object *obj)
+{
+ while (list) {
+ if (list->item == obj)
+ return 1;
+ list = list->next;
+ }
+ return 0;
+}
diff --git a/object.h b/object.h
new file mode 100644
index 000000000..336d986b5
--- /dev/null
+++ b/object.h
@@ -0,0 +1,58 @@
+#ifndef OBJECT_H
+#define OBJECT_H
+
+struct object_list {
+ struct object *item;
+ struct object_list *next;
+ const char *name;
+};
+
+struct object_refs {
+ unsigned count;
+ struct object *ref[0];
+};
+
+struct object {
+ unsigned parsed : 1;
+ unsigned used : 1;
+ unsigned int flags;
+ unsigned char sha1[20];
+ const char *type;
+ struct object_refs *refs;
+ void *util;
+};
+
+extern int track_object_refs;
+extern int nr_objs;
+extern struct object **objs;
+
+/** Internal only **/
+struct object *lookup_object(const unsigned char *sha1);
+
+/** Returns the object, having looked it up as being the given type. **/
+struct object *lookup_object_type(const unsigned char *sha1, const char *type);
+
+void created_object(const unsigned char *sha1, struct object *obj);
+
+/** Returns the object, having parsed it to find out what it is. **/
+struct object *parse_object(const unsigned char *sha1);
+
+/** Returns the object, with potentially excess memory allocated. **/
+struct object *lookup_unknown_object(const unsigned char *sha1);
+
+struct object_refs *alloc_object_refs(unsigned count);
+void set_object_refs(struct object *obj, struct object_refs *refs);
+
+void mark_reachable(struct object *obj, unsigned int mask);
+
+struct object_list *object_list_insert(struct object *item,
+ struct object_list **list_p);
+
+void object_list_append(struct object *item,
+ struct object_list **list_p);
+
+unsigned object_list_length(struct object_list *list);
+
+int object_list_contains(struct object_list *list, struct object *obj);
+
+#endif /* OBJECT_H */
diff --git a/pack-check.c b/pack-check.c
new file mode 100644
index 000000000..511f29424
--- /dev/null
+++ b/pack-check.c
@@ -0,0 +1,143 @@
+#include "cache.h"
+#include "pack.h"
+
+static int verify_packfile(struct packed_git *p)
+{
+ unsigned long index_size = p->index_size;
+ void *index_base = p->index_base;
+ SHA_CTX ctx;
+ unsigned char sha1[20];
+ unsigned long pack_size = p->pack_size;
+ void *pack_base;
+ struct pack_header *hdr;
+ int nr_objects, err, i;
+
+ /* Header consistency check */
+ hdr = p->pack_base;
+ if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+ return error("Packfile %s signature mismatch", p->pack_name);
+ if (hdr->hdr_version != htonl(PACK_VERSION))
+ return error("Packfile version %d different from ours %d",
+ ntohl(hdr->hdr_version), PACK_VERSION);
+ nr_objects = ntohl(hdr->hdr_entries);
+ if (num_packed_objects(p) != nr_objects)
+ return error("Packfile claims to have %d objects, "
+ "while idx size expects %d", nr_objects,
+ num_packed_objects(p));
+
+ SHA1_Init(&ctx);
+ pack_base = p->pack_base;
+ SHA1_Update(&ctx, pack_base, pack_size - 20);
+ SHA1_Final(sha1, &ctx);
+ if (memcmp(sha1, index_base + index_size - 40, 20))
+ return error("Packfile %s SHA1 mismatch with idx",
+ p->pack_name);
+ if (memcmp(sha1, pack_base + pack_size - 20, 20))
+ return error("Packfile %s SHA1 mismatch with itself",
+ p->pack_name);
+
+ /* Make sure everything reachable from idx is valid. Since we
+ * have verified that nr_objects matches between idx and pack,
+ * we do not do scan-streaming check on the pack file.
+ */
+ for (i = err = 0; i < nr_objects; i++) {
+ unsigned char sha1[20];
+ struct pack_entry e;
+ void *data;
+ char type[20];
+ unsigned long size;
+
+ if (nth_packed_object_sha1(p, i, sha1))
+ die("internal error pack-check nth-packed-object");
+ if (!find_pack_entry_one(sha1, &e, p))
+ die("internal error pack-check find-pack-entry-one");
+ data = unpack_entry_gently(&e, type, &size);
+ if (!data) {
+ err = error("cannot unpack %s from %s",
+ sha1_to_hex(sha1), p->pack_name);
+ continue;
+ }
+ if (check_sha1_signature(sha1, data, size, type)) {
+ err = error("packed %s from %s is corrupt",
+ sha1_to_hex(sha1), p->pack_name);
+ free(data);
+ continue;
+ }
+ free(data);
+ }
+
+ return err;
+}
+
+
+static void show_pack_info(struct packed_git *p)
+{
+ struct pack_header *hdr;
+ int nr_objects, i;
+
+ hdr = p->pack_base;
+ nr_objects = ntohl(hdr->hdr_entries);
+
+ for (i = 0; i < nr_objects; i++) {
+ unsigned char sha1[20], base_sha1[20];
+ struct pack_entry e;
+ char type[20];
+ unsigned long size;
+ unsigned long store_size;
+ int delta_chain_length;
+
+ if (nth_packed_object_sha1(p, i, sha1))
+ die("internal error pack-check nth-packed-object");
+ if (!find_pack_entry_one(sha1, &e, p))
+ die("internal error pack-check find-pack-entry-one");
+
+ packed_object_info_detail(&e, type, &size, &store_size,
+ &delta_chain_length,
+ base_sha1);
+ printf("%s ", sha1_to_hex(sha1));
+ if (!delta_chain_length)
+ printf("%-6s %lu %u\n", type, size, e.offset);
+ else
+ printf("%-6s %lu %u %d %s\n", type, size, e.offset,
+ delta_chain_length, sha1_to_hex(base_sha1));
+ }
+
+}
+
+int verify_pack(struct packed_git *p, int verbose)
+{
+ unsigned long index_size = p->index_size;
+ void *index_base = p->index_base;
+ SHA_CTX ctx;
+ unsigned char sha1[20];
+ int ret;
+
+ ret = 0;
+ /* Verify SHA1 sum of the index file */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, index_base, index_size - 20);
+ SHA1_Final(sha1, &ctx);
+ if (memcmp(sha1, index_base + index_size - 20, 20))
+ ret = error("Packfile index for %s SHA1 mismatch",
+ p->pack_name);
+
+ if (!ret) {
+ /* Verify pack file */
+ use_packed_git(p);
+ ret = verify_packfile(p);
+ unuse_packed_git(p);
+ }
+
+ if (verbose) {
+ if (ret)
+ printf("%s: bad\n", p->pack_name);
+ else {
+ use_packed_git(p);
+ show_pack_info(p);
+ unuse_packed_git(p);
+ printf("%s: ok\n", p->pack_name);
+ }
+ }
+
+ return ret;
+}
diff --git a/pack-objects.c b/pack-objects.c
new file mode 100644
index 000000000..4e941e739
--- /dev/null
+++ b/pack-objects.c
@@ -0,0 +1,560 @@
+#include "cache.h"
+#include "object.h"
+#include "delta.h"
+#include "pack.h"
+#include "csum-file.h"
+
+static const char pack_usage[] = "git-pack-objects [--local] [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
+
+struct object_entry {
+ unsigned char sha1[20];
+ unsigned long size;
+ unsigned long offset;
+ unsigned int depth;
+ unsigned int hash;
+ enum object_type type;
+ unsigned long delta_size;
+ struct object_entry *delta;
+};
+
+static unsigned char object_list_sha1[20];
+static int non_empty = 0;
+static int local = 0;
+static int incremental = 0;
+static struct object_entry **sorted_by_sha, **sorted_by_type;
+static struct object_entry *objects = NULL;
+static int nr_objects = 0, nr_alloc = 0;
+static const char *base_name;
+static unsigned char pack_file_sha1[20];
+
+static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
+{
+ unsigned long othersize, delta_size;
+ char type[10];
+ void *otherbuf = read_sha1_file(entry->delta->sha1, type, &othersize);
+ void *delta_buf;
+
+ if (!otherbuf)
+ die("unable to read %s", sha1_to_hex(entry->delta->sha1));
+ delta_buf = diff_delta(otherbuf, othersize,
+ buf, size, &delta_size, 0);
+ if (!delta_buf || delta_size != entry->delta_size)
+ die("delta size changed");
+ free(buf);
+ free(otherbuf);
+ return delta_buf;
+}
+
+/*
+ * The per-object header is a pretty dense thing, which is
+ * - first byte: low four bits are "size", then three bits of "type",
+ * and the high bit is "size continues".
+ * - each byte afterwards: low seven bits are size continuation,
+ * with the high bit being "size continues"
+ */
+static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr)
+{
+ int n = 1;
+ unsigned char c;
+
+ if (type < OBJ_COMMIT || type > OBJ_DELTA)
+ die("bad type %d", type);
+
+ c = (type << 4) | (size & 15);
+ size >>= 4;
+ while (size) {
+ *hdr++ = c | 0x80;
+ c = size & 0x7f;
+ size >>= 7;
+ n++;
+ }
+ *hdr = c;
+ return n;
+}
+
+static unsigned long write_object(struct sha1file *f, struct object_entry *entry)
+{
+ unsigned long size;
+ char type[10];
+ void *buf = read_sha1_file(entry->sha1, type, &size);
+ unsigned char header[10];
+ unsigned hdrlen, datalen;
+ enum object_type obj_type;
+
+ if (!buf)
+ die("unable to read %s", sha1_to_hex(entry->sha1));
+ if (size != entry->size)
+ die("object %s size inconsistency (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
+
+ /*
+ * The object header is a byte of 'type' followed by zero or
+ * more bytes of length. For deltas, the 20 bytes of delta sha1
+ * follows that.
+ */
+ obj_type = entry->type;
+ if (entry->delta) {
+ buf = delta_against(buf, size, entry);
+ size = entry->delta_size;
+ obj_type = OBJ_DELTA;
+ }
+ hdrlen = encode_header(obj_type, size, header);
+ sha1write(f, header, hdrlen);
+ if (entry->delta) {
+ sha1write(f, entry->delta, 20);
+ hdrlen += 20;
+ }
+ datalen = sha1write_compressed(f, buf, size);
+ free(buf);
+ return hdrlen + datalen;
+}
+
+static unsigned long write_one(struct sha1file *f,
+ struct object_entry *e,
+ unsigned long offset)
+{
+ if (e->offset)
+ /* offset starts from header size and cannot be zero
+ * if it is written already.
+ */
+ return offset;
+ e->offset = offset;
+ offset += write_object(f, e);
+ /* if we are delitified, write out its base object. */
+ if (e->delta)
+ offset = write_one(f, e->delta, offset);
+ return offset;
+}
+
+static void write_pack_file(void)
+{
+ int i;
+ struct sha1file *f;
+ unsigned long offset;
+ unsigned long mb;
+ struct pack_header hdr;
+
+ if (!base_name)
+ f = sha1fd(1, "<stdout>");
+ else
+ f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "pack");
+ hdr.hdr_signature = htonl(PACK_SIGNATURE);
+ hdr.hdr_version = htonl(PACK_VERSION);
+ hdr.hdr_entries = htonl(nr_objects);
+ sha1write(f, &hdr, sizeof(hdr));
+ offset = sizeof(hdr);
+ for (i = 0; i < nr_objects; i++)
+ offset = write_one(f, objects + i, offset);
+
+ sha1close(f, pack_file_sha1, 1);
+ mb = offset >> 20;
+ offset &= 0xfffff;
+}
+
+static void write_index_file(void)
+{
+ int i;
+ struct sha1file *f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "idx");
+ struct object_entry **list = sorted_by_sha;
+ struct object_entry **last = list + nr_objects;
+ unsigned int array[256];
+
+ /*
+ * Write the first-level table (the list is sorted,
+ * but we use a 256-entry lookup to be able to avoid
+ * having to do eight extra binary search iterations).
+ */
+ for (i = 0; i < 256; i++) {
+ struct object_entry **next = list;
+ while (next < last) {
+ struct object_entry *entry = *next;
+ if (entry->sha1[0] != i)
+ break;
+ next++;
+ }
+ array[i] = htonl(next - sorted_by_sha);
+ list = next;
+ }
+ sha1write(f, array, 256 * sizeof(int));
+
+ /*
+ * Write the actual SHA1 entries..
+ */
+ list = sorted_by_sha;
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *entry = *list++;
+ unsigned int offset = htonl(entry->offset);
+ sha1write(f, &offset, 4);
+ sha1write(f, entry->sha1, 20);
+ }
+ sha1write(f, pack_file_sha1, 20);
+ sha1close(f, NULL, 1);
+}
+
+static int add_object_entry(unsigned char *sha1, unsigned int hash)
+{
+ unsigned int idx = nr_objects;
+ struct object_entry *entry;
+
+ if (incremental || local) {
+ struct packed_git *p;
+
+ for (p = packed_git; p; p = p->next) {
+ struct pack_entry e;
+
+ if (find_pack_entry_one(sha1, &e, p)) {
+ if (incremental)
+ return 0;
+ if (local && !p->pack_local)
+ return 0;
+ }
+ }
+ }
+
+ if (idx >= nr_alloc) {
+ unsigned int needed = (idx + 1024) * 3 / 2;
+ objects = xrealloc(objects, needed * sizeof(*entry));
+ nr_alloc = needed;
+ }
+ entry = objects + idx;
+ memset(entry, 0, sizeof(*entry));
+ memcpy(entry->sha1, sha1, 20);
+ entry->hash = hash;
+ nr_objects = idx+1;
+ return 1;
+}
+
+static void check_object(struct object_entry *entry)
+{
+ char type[20];
+
+ if (!sha1_object_info(entry->sha1, type, &entry->size)) {
+ if (!strcmp(type, "commit")) {
+ entry->type = OBJ_COMMIT;
+ } else if (!strcmp(type, "tree")) {
+ entry->type = OBJ_TREE;
+ } else if (!strcmp(type, "blob")) {
+ entry->type = OBJ_BLOB;
+ } else if (!strcmp(type, "tag")) {
+ entry->type = OBJ_TAG;
+ } else
+ die("unable to pack object %s of type %s",
+ sha1_to_hex(entry->sha1), type);
+ }
+ else
+ die("unable to get type of object %s",
+ sha1_to_hex(entry->sha1));
+}
+
+static void get_object_details(void)
+{
+ int i;
+ struct object_entry *entry = objects;
+
+ for (i = 0; i < nr_objects; i++)
+ check_object(entry++);
+}
+
+typedef int (*entry_sort_t)(const struct object_entry *, const struct object_entry *);
+
+static entry_sort_t current_sort;
+
+static int sort_comparator(const void *_a, const void *_b)
+{
+ struct object_entry *a = *(struct object_entry **)_a;
+ struct object_entry *b = *(struct object_entry **)_b;
+ return current_sort(a,b);
+}
+
+static struct object_entry **create_sorted_list(entry_sort_t sort)
+{
+ struct object_entry **list = xmalloc(nr_objects * sizeof(struct object_entry *));
+ int i;
+
+ for (i = 0; i < nr_objects; i++)
+ list[i] = objects + i;
+ current_sort = sort;
+ qsort(list, nr_objects, sizeof(struct object_entry *), sort_comparator);
+ return list;
+}
+
+static int sha1_sort(const struct object_entry *a, const struct object_entry *b)
+{
+ return memcmp(a->sha1, b->sha1, 20);
+}
+
+static int type_size_sort(const struct object_entry *a, const struct object_entry *b)
+{
+ if (a->type < b->type)
+ return -1;
+ if (a->type > b->type)
+ return 1;
+ if (a->hash < b->hash)
+ return -1;
+ if (a->hash > b->hash)
+ return 1;
+ if (a->size < b->size)
+ return -1;
+ if (a->size > b->size)
+ return 1;
+ return a < b ? -1 : (a > b);
+}
+
+struct unpacked {
+ struct object_entry *entry;
+ void *data;
+};
+
+/*
+ * We search for deltas _backwards_ in a list sorted by type and
+ * by size, so that we see progressively smaller and smaller files.
+ * That's because we prefer deltas to be from the bigger file
+ * to the smaller - deletes are potentially cheaper, but perhaps
+ * more importantly, the bigger file is likely the more recent
+ * one.
+ */
+static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_depth)
+{
+ struct object_entry *cur_entry = cur->entry;
+ struct object_entry *old_entry = old->entry;
+ unsigned long size, oldsize, delta_size, sizediff;
+ long max_size;
+ void *delta_buf;
+
+ /* Don't bother doing diffs between different types */
+ if (cur_entry->type != old_entry->type)
+ return -1;
+
+ size = cur_entry->size;
+ if (size < 50)
+ return -1;
+ oldsize = old_entry->size;
+ sizediff = oldsize > size ? oldsize - size : size - oldsize;
+ if (sizediff > size / 8)
+ return -1;
+ if (old_entry->depth >= max_depth)
+ return 0;
+
+ /*
+ * NOTE!
+ *
+ * We always delta from the bigger to the smaller, since that's
+ * more space-efficient (deletes don't have to say _what_ they
+ * delete).
+ */
+ max_size = size / 2 - 20;
+ if (cur_entry->delta)
+ max_size = cur_entry->delta_size-1;
+ if (sizediff >= max_size)
+ return -1;
+ delta_buf = diff_delta(old->data, oldsize,
+ cur->data, size, &delta_size, max_size);
+ if (!delta_buf)
+ return 0;
+ cur_entry->delta = old_entry;
+ cur_entry->delta_size = delta_size;
+ cur_entry->depth = old_entry->depth + 1;
+ free(delta_buf);
+ return 0;
+}
+
+static void find_deltas(struct object_entry **list, int window, int depth)
+{
+ int i, idx;
+ unsigned int array_size = window * sizeof(struct unpacked);
+ struct unpacked *array = xmalloc(array_size);
+
+ memset(array, 0, array_size);
+ i = nr_objects;
+ idx = 0;
+ while (--i >= 0) {
+ struct object_entry *entry = list[i];
+ struct unpacked *n = array + idx;
+ unsigned long size;
+ char type[10];
+ int j;
+
+ free(n->data);
+ n->entry = entry;
+ n->data = read_sha1_file(entry->sha1, type, &size);
+ if (size != entry->size)
+ die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
+ j = window;
+ while (--j > 0) {
+ unsigned int other_idx = idx + j;
+ struct unpacked *m;
+ if (other_idx >= window)
+ other_idx -= window;
+ m = array + other_idx;
+ if (!m->entry)
+ break;
+ if (try_delta(n, m, depth) < 0)
+ break;
+ }
+ idx++;
+ if (idx >= window)
+ idx = 0;
+ }
+
+ for (i = 0; i < window; ++i)
+ free(array[i].data);
+ free(array);
+}
+
+static void prepare_pack(int window, int depth)
+{
+ get_object_details();
+
+ fprintf(stderr, "Packing %d objects\n", nr_objects);
+
+ sorted_by_type = create_sorted_list(type_size_sort);
+ if (window && depth)
+ find_deltas(sorted_by_type, window+1, depth);
+ write_pack_file();
+}
+
+static int reuse_cached_pack(unsigned char *sha1, int pack_to_stdout)
+{
+ static const char cache[] = "pack-cache/pack-%s.%s";
+ char *cached_pack, *cached_idx;
+ int ifd, ofd, ifd_ix = -1;
+
+ cached_pack = git_path(cache, sha1_to_hex(sha1), "pack");
+ ifd = open(cached_pack, O_RDONLY);
+ if (ifd < 0)
+ return 0;
+
+ if (!pack_to_stdout) {
+ cached_idx = git_path(cache, sha1_to_hex(sha1), "idx");
+ ifd_ix = open(cached_idx, O_RDONLY);
+ if (ifd_ix < 0) {
+ close(ifd);
+ return 0;
+ }
+ }
+
+ fprintf(stderr, "Reusing %d objects pack %s\n", nr_objects,
+ sha1_to_hex(sha1));
+
+ if (pack_to_stdout) {
+ if (copy_fd(ifd, 1))
+ exit(1);
+ close(ifd);
+ }
+ else {
+ char name[PATH_MAX];
+ snprintf(name, sizeof(name),
+ "%s-%s.%s", base_name, sha1_to_hex(sha1), "pack");
+ ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ if (ofd < 0)
+ die("unable to open %s (%s)", name, strerror(errno));
+ if (copy_fd(ifd, ofd))
+ exit(1);
+ close(ifd);
+
+ snprintf(name, sizeof(name),
+ "%s-%s.%s", base_name, sha1_to_hex(sha1), "idx");
+ ofd = open(name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ if (ofd < 0)
+ die("unable to open %s (%s)", name, strerror(errno));
+ if (copy_fd(ifd_ix, ofd))
+ exit(1);
+ close(ifd_ix);
+ puts(sha1_to_hex(sha1));
+ }
+
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ SHA_CTX ctx;
+ char line[PATH_MAX + 20];
+ int window = 10, depth = 10, pack_to_stdout = 0;
+ struct object_entry **list;
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strcmp("--non-empty", arg)) {
+ non_empty = 1;
+ continue;
+ }
+ if (!strcmp("--local", arg)) {
+ local = 1;
+ continue;
+ }
+ if (!strcmp("--incremental", arg)) {
+ incremental = 1;
+ continue;
+ }
+ if (!strncmp("--window=", arg, 9)) {
+ char *end;
+ window = strtoul(arg+9, &end, 0);
+ if (!arg[9] || *end)
+ usage(pack_usage);
+ continue;
+ }
+ if (!strncmp("--depth=", arg, 8)) {
+ char *end;
+ depth = strtoul(arg+8, &end, 0);
+ if (!arg[8] || *end)
+ usage(pack_usage);
+ continue;
+ }
+ if (!strcmp("--stdout", arg)) {
+ pack_to_stdout = 1;
+ continue;
+ }
+ usage(pack_usage);
+ }
+ if (base_name)
+ usage(pack_usage);
+ base_name = arg;
+ }
+
+ if (pack_to_stdout != !base_name)
+ usage(pack_usage);
+
+ prepare_packed_git();
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ unsigned int hash;
+ char *p;
+ unsigned char sha1[20];
+
+ if (get_sha1_hex(line, sha1))
+ die("expected sha1, got garbage");
+ hash = 0;
+ p = line+40;
+ while (*p) {
+ unsigned char c = *p++;
+ if (isspace(c))
+ continue;
+ hash = hash * 11 + c;
+ }
+ add_object_entry(sha1, hash);
+ }
+ if (non_empty && !nr_objects)
+ return 0;
+
+ sorted_by_sha = create_sorted_list(sha1_sort);
+ SHA1_Init(&ctx);
+ list = sorted_by_sha;
+ for (i = 0; i < nr_objects; i++) {
+ struct object_entry *entry = *list++;
+ SHA1_Update(&ctx, entry->sha1, 20);
+ }
+ SHA1_Final(object_list_sha1, &ctx);
+
+ if (reuse_cached_pack(object_list_sha1, pack_to_stdout))
+ ;
+ else {
+ prepare_pack(window, depth);
+ if (!pack_to_stdout) {
+ write_index_file();
+ puts(sha1_to_hex(object_list_sha1));
+ }
+ }
+ return 0;
+}
diff --git a/pack-redundant.c b/pack-redundant.c
new file mode 100644
index 000000000..fb6cb4850
--- /dev/null
+++ b/pack-redundant.c
@@ -0,0 +1,652 @@
+/*
+*
+* Copyright 2005, Lukas Sandstrom <lukass@etek.chalmers.se>
+*
+* This file is licensed under the GPL v2.
+*
+*/
+
+#include "cache.h"
+
+static const char pack_redundant_usage[] =
+"git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>";
+
+int load_all_packs = 0, verbose = 0, alt_odb = 0;
+
+struct llist_item {
+ struct llist_item *next;
+ char *sha1;
+};
+struct llist {
+ struct llist_item *front;
+ struct llist_item *back;
+ size_t size;
+} *all_objects; /* all objects which must be present in local packfiles */
+
+struct pack_list {
+ struct pack_list *next;
+ struct packed_git *pack;
+ struct llist *unique_objects;
+ struct llist *all_objects;
+} *local_packs = NULL, *altodb_packs = NULL;
+
+struct pll {
+ struct pll *next;
+ struct pack_list *pl;
+ size_t pl_size;
+};
+
+inline void llist_free(struct llist *list)
+{
+ while((list->back = list->front)) {
+ list->front = list->front->next;
+ free(list->back);
+ }
+ free(list);
+}
+
+inline void llist_init(struct llist **list)
+{
+ *list = xmalloc(sizeof(struct llist));
+ (*list)->front = (*list)->back = NULL;
+ (*list)->size = 0;
+}
+
+struct llist * llist_copy(struct llist *list)
+{
+ struct llist *ret;
+ struct llist_item *new, *old, *prev;
+
+ llist_init(&ret);
+
+ if ((ret->size = list->size) == 0)
+ return ret;
+
+ new = ret->front = xmalloc(sizeof(struct llist_item));
+ new->sha1 = list->front->sha1;
+
+ old = list->front->next;
+ while (old) {
+ prev = new;
+ new = xmalloc(sizeof(struct llist_item));
+ prev->next = new;
+ new->sha1 = old->sha1;
+ old = old->next;
+ }
+ new->next = NULL;
+ ret->back = new;
+
+ return ret;
+}
+
+inline struct llist_item * llist_insert(struct llist *list,
+ struct llist_item *after, char *sha1)
+{
+ struct llist_item *new = xmalloc(sizeof(struct llist_item));
+ new->sha1 = sha1;
+ new->next = NULL;
+
+ if (after != NULL) {
+ new->next = after->next;
+ after->next = new;
+ if (after == list->back)
+ list->back = new;
+ } else {/* insert in front */
+ if (list->size == 0)
+ list->back = new;
+ else
+ new->next = list->front;
+ list->front = new;
+ }
+ list->size++;
+ return new;
+}
+
+inline struct llist_item * llist_insert_back(struct llist *list, char *sha1)
+{
+ return llist_insert(list, list->back, sha1);
+}
+
+inline struct llist_item * llist_insert_sorted_unique(struct llist *list,
+ char *sha1, struct llist_item *hint)
+{
+ struct llist_item *prev = NULL, *l;
+
+ l = (hint == NULL) ? list->front : hint;
+ while (l) {
+ int cmp = memcmp(l->sha1, sha1, 20);
+ if (cmp > 0) { /* we insert before this entry */
+ return llist_insert(list, prev, sha1);
+ }
+ if(!cmp) { /* already exists */
+ return l;
+ }
+ prev = l;
+ l = l->next;
+ }
+ /* insert at the end */
+ return llist_insert_back(list, sha1);
+}
+
+/* returns a pointer to an item in front of sha1 */
+inline struct llist_item * llist_sorted_remove(struct llist *list, char *sha1,
+ struct llist_item *hint)
+{
+ struct llist_item *prev, *l;
+
+redo_from_start:
+ l = (hint == NULL) ? list->front : hint;
+ prev = NULL;
+ while (l) {
+ int cmp = memcmp(l->sha1, sha1, 20);
+ if (cmp > 0) /* not in list, since sorted */
+ return prev;
+ if(!cmp) { /* found */
+ if (prev == NULL) {
+ if (hint != NULL && hint != list->front) {
+ /* we don't know the previous element */
+ hint = NULL;
+ goto redo_from_start;
+ }
+ list->front = l->next;
+ } else
+ prev->next = l->next;
+ if (l == list->back)
+ list->back = prev;
+ free(l);
+ list->size--;
+ return prev;
+ }
+ prev = l;
+ l = l->next;
+ }
+ return prev;
+}
+
+/* computes A\B */
+void llist_sorted_difference_inplace(struct llist *A,
+ struct llist *B)
+{
+ struct llist_item *hint, *b;
+
+ hint = NULL;
+ b = B->front;
+
+ while (b) {
+ hint = llist_sorted_remove(A, b->sha1, hint);
+ b = b->next;
+ }
+}
+
+inline struct pack_list * pack_list_insert(struct pack_list **pl,
+ struct pack_list *entry)
+{
+ struct pack_list *p = xmalloc(sizeof(struct pack_list));
+ memcpy(p, entry, sizeof(struct pack_list));
+ p->next = *pl;
+ *pl = p;
+ return p;
+}
+
+inline size_t pack_list_size(struct pack_list *pl)
+{
+ size_t ret = 0;
+ while(pl) {
+ ret++;
+ pl = pl->next;
+ }
+ return ret;
+}
+
+struct pack_list * pack_list_difference(struct pack_list *A,
+ struct pack_list *B)
+{
+ struct pack_list *ret, *pl;
+
+ if (A == NULL)
+ return NULL;
+
+ pl = B;
+ while (pl != NULL) {
+ if (A->pack == pl->pack)
+ return pack_list_difference(A->next, B);
+ pl = pl->next;
+ }
+ ret = xmalloc(sizeof(struct pack_list));
+ memcpy(ret, A, sizeof(struct pack_list));
+ ret->next = pack_list_difference(A->next, B);
+ return ret;
+}
+
+void cmp_two_packs(struct pack_list *p1, struct pack_list *p2)
+{
+ int p1_off, p2_off;
+ void *p1_base, *p2_base;
+ struct llist_item *p1_hint = NULL, *p2_hint = NULL;
+
+ p1_off = p2_off = 256 * 4 + 4;
+ p1_base = (void *)p1->pack->index_base;
+ p2_base = (void *)p2->pack->index_base;
+
+ while (p1_off <= p1->pack->index_size - 3 * 20 &&
+ p2_off <= p2->pack->index_size - 3 * 20)
+ {
+ int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20);
+ /* cmp ~ p1 - p2 */
+ if (cmp == 0) {
+ p1_hint = llist_sorted_remove(p1->unique_objects,
+ p1_base + p1_off, p1_hint);
+ p2_hint = llist_sorted_remove(p2->unique_objects,
+ p1_base + p1_off, p2_hint);
+ p1_off+=24;
+ p2_off+=24;
+ continue;
+ }
+ if (cmp < 0) { /* p1 has the object, p2 doesn't */
+ p1_off+=24;
+ } else { /* p2 has the object, p1 doesn't */
+ p2_off+=24;
+ }
+ }
+}
+
+void pll_insert(struct pll **pll, struct pll **hint_table)
+{
+ struct pll *prev;
+ int i = (*pll)->pl_size - 1;
+
+ if (hint_table[i] == NULL) {
+ hint_table[i--] = *pll;
+ for (; i >= 0; --i) {
+ if (hint_table[i] != NULL)
+ break;
+ }
+ if (hint_table[i] == NULL) /* no elements in list */
+ die("Why did this happen?");
+ }
+
+ prev = hint_table[i];
+ while (prev->next && prev->next->pl_size < (*pll)->pl_size)
+ prev = prev->next;
+
+ (*pll)->next = prev->next;
+ prev->next = *pll;
+}
+
+/* all the permutations have to be free()d at the same time,
+ * since they refer to each other
+ */
+struct pll * get_all_permutations(struct pack_list *list)
+{
+ struct pll *subset, *pll, *new_pll = NULL; /*silence warning*/
+ static struct pll **hint = NULL;
+ if (hint == NULL)
+ hint = xcalloc(pack_list_size(list), sizeof(struct pll *));
+
+ if (list == NULL)
+ return NULL;
+
+ if (list->next == NULL) {
+ new_pll = xmalloc(sizeof(struct pll));
+ hint[0] = new_pll;
+ new_pll->next = NULL;
+ new_pll->pl = list;
+ new_pll->pl_size = 1;
+ return new_pll;
+ }
+
+ pll = subset = get_all_permutations(list->next);
+ while (pll) {
+ if (pll->pl->pack == list->pack) {
+ pll = pll->next;
+ continue;
+ }
+ new_pll = xmalloc(sizeof(struct pll));
+
+ new_pll->pl = xmalloc(sizeof(struct pack_list));
+ memcpy(new_pll->pl, list, sizeof(struct pack_list));
+ new_pll->pl->next = pll->pl;
+ new_pll->pl_size = pll->pl_size + 1;
+
+ pll_insert(&new_pll, hint);
+
+ pll = pll->next;
+ }
+ /* add ourself */
+ new_pll = xmalloc(sizeof(struct pll));
+ new_pll->pl = xmalloc(sizeof(struct pack_list));
+ memcpy(new_pll->pl, list, sizeof(struct pack_list));
+ new_pll->pl->next = NULL;
+ new_pll->pl_size = 1;
+ pll_insert(&new_pll, hint);
+
+ return hint[0];
+}
+
+int is_superset(struct pack_list *pl, struct llist *list)
+{
+ struct llist *diff;
+
+ diff = llist_copy(list);
+
+ while (pl) {
+ llist_sorted_difference_inplace(diff,
+ pl->all_objects);
+ if (diff->size == 0) { /* we're done */
+ llist_free(diff);
+ return 1;
+ }
+ pl = pl->next;
+ }
+ llist_free(diff);
+ return 0;
+}
+
+size_t sizeof_union(struct packed_git *p1, struct packed_git *p2)
+{
+ size_t ret = 0;
+ int p1_off, p2_off;
+ void *p1_base, *p2_base;
+
+ p1_off = p2_off = 256 * 4 + 4;
+ p1_base = (void *)p1->index_base;
+ p2_base = (void *)p2->index_base;
+
+ while (p1_off <= p1->index_size - 3 * 20 &&
+ p2_off <= p2->index_size - 3 * 20)
+ {
+ int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20);
+ /* cmp ~ p1 - p2 */
+ if (cmp == 0) {
+ ret++;
+ p1_off+=24;
+ p2_off+=24;
+ continue;
+ }
+ if (cmp < 0) { /* p1 has the object, p2 doesn't */
+ p1_off+=24;
+ } else { /* p2 has the object, p1 doesn't */
+ p2_off+=24;
+ }
+ }
+ return ret;
+}
+
+/* another O(n^2) function ... */
+size_t get_pack_redundancy(struct pack_list *pl)
+{
+ struct pack_list *subset;
+
+ if (pl == NULL)
+ return 0;
+
+ size_t ret = 0;
+ while ((subset = pl->next)) {
+ while(subset) {
+ ret += sizeof_union(pl->pack, subset->pack);
+ subset = subset->next;
+ }
+ pl = pl->next;
+ }
+ return ret;
+}
+
+inline size_t pack_set_bytecount(struct pack_list *pl)
+{
+ size_t ret = 0;
+ while (pl) {
+ ret += pl->pack->pack_size;
+ ret += pl->pack->index_size;
+ pl = pl->next;
+ }
+ return ret;
+}
+
+void minimize(struct pack_list **min)
+{
+ struct pack_list *pl, *unique = NULL,
+ *non_unique = NULL, *min_perm = NULL;
+ struct pll *perm, *perm_all, *perm_ok = NULL, *new_perm;
+ struct llist *missing;
+ size_t min_perm_size = (size_t)-1, perm_size;
+
+ pl = local_packs;
+ while (pl) {
+ if(pl->unique_objects->size)
+ pack_list_insert(&unique, pl);
+ else
+ pack_list_insert(&non_unique, pl);
+ pl = pl->next;
+ }
+ /* find out which objects are missing from the set of unique packs */
+ missing = llist_copy(all_objects);
+ pl = unique;
+ while (pl) {
+ llist_sorted_difference_inplace(missing,
+ pl->all_objects);
+ pl = pl->next;
+ }
+
+ /* return if there are no objects missing from the unique set */
+ if (missing->size == 0) {
+ *min = unique;
+ return;
+ }
+
+ /* find the permutations which contain all missing objects */
+ perm_all = perm = get_all_permutations(non_unique);
+ while (perm) {
+ if (perm_ok && perm->pl_size > perm_ok->pl_size)
+ break; /* ignore all larger permutations */
+ if (is_superset(perm->pl, missing)) {
+ new_perm = xmalloc(sizeof(struct pll));
+ memcpy(new_perm, perm, sizeof(struct pll));
+ new_perm->next = perm_ok;
+ perm_ok = new_perm;
+ }
+ perm = perm->next;
+ }
+
+ if (perm_ok == NULL)
+ die("Internal error: No complete sets found!\n");
+
+ /* find the permutation with the smallest size */
+ perm = perm_ok;
+ while (perm) {
+ perm_size = pack_set_bytecount(perm->pl);
+ if (min_perm_size > perm_size) {
+ min_perm_size = perm_size;
+ min_perm = perm->pl;
+ }
+ perm = perm->next;
+ }
+ *min = min_perm;
+ /* add the unique packs to the list */
+ pl = unique;
+ while(pl) {
+ pack_list_insert(min, pl);
+ pl = pl->next;
+ }
+}
+
+void load_all_objects()
+{
+ struct pack_list *pl = local_packs;
+ struct llist_item *hint, *l;
+ int i;
+
+ llist_init(&all_objects);
+
+ while (pl) {
+ i = 0;
+ hint = NULL;
+ l = pl->all_objects->front;
+ while (l) {
+ hint = llist_insert_sorted_unique(all_objects,
+ l->sha1, hint);
+ l = l->next;
+ }
+ pl = pl->next;
+ }
+ /* remove objects present in remote packs */
+ pl = altodb_packs;
+ while (pl) {
+ llist_sorted_difference_inplace(all_objects, pl->all_objects);
+ pl = pl->next;
+ }
+}
+
+/* this scales like O(n^2) */
+void cmp_local_packs()
+{
+ struct pack_list *subset, *pl = local_packs;
+
+ while ((subset = pl)) {
+ while((subset = subset->next))
+ cmp_two_packs(pl, subset);
+ pl = pl->next;
+ }
+}
+
+void scan_alt_odb_packs()
+{
+ struct pack_list *local, *alt;
+
+ alt = altodb_packs;
+ while (alt) {
+ local = local_packs;
+ while (local) {
+ llist_sorted_difference_inplace(local->unique_objects,
+ alt->all_objects);
+ local = local->next;
+ }
+ alt = alt->next;
+ }
+}
+
+struct pack_list * add_pack(struct packed_git *p)
+{
+ struct pack_list l;
+ size_t off;
+ void *base;
+
+ if (!p->pack_local && !(alt_odb || verbose))
+ return NULL;
+
+ l.pack = p;
+ llist_init(&l.all_objects);
+
+ off = 256 * 4 + 4;
+ base = (void *)p->index_base;
+ while (off <= p->index_size - 3 * 20) {
+ llist_insert_back(l.all_objects, base + off);
+ off += 24;
+ }
+ /* this list will be pruned in cmp_two_packs later */
+ l.unique_objects = llist_copy(l.all_objects);
+ if (p->pack_local)
+ return pack_list_insert(&local_packs, &l);
+ else
+ return pack_list_insert(&altodb_packs, &l);
+}
+
+struct pack_list * add_pack_file(char *filename)
+{
+ struct packed_git *p = packed_git;
+
+ if (strlen(filename) < 40)
+ die("Bad pack filename: %s\n", filename);
+
+ while (p) {
+ if (strstr(p->pack_name, filename))
+ return add_pack(p);
+ p = p->next;
+ }
+ die("Filename %s not found in packed_git\n", filename);
+}
+
+void load_all()
+{
+ struct packed_git *p = packed_git;
+
+ while (p) {
+ add_pack(p);
+ p = p->next;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ struct pack_list *min, *red, *pl;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if(!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if(!strcmp(arg, "--all")) {
+ load_all_packs = 1;
+ continue;
+ }
+ if(!strcmp(arg, "--verbose")) {
+ verbose = 1;
+ continue;
+ }
+ if(!strcmp(arg, "--alt-odb")) {
+ alt_odb = 1;
+ continue;
+ }
+ if(*arg == '-')
+ usage(pack_redundant_usage);
+ else
+ break;
+ }
+
+ prepare_packed_git();
+
+ if (load_all_packs)
+ load_all();
+ else
+ while (*(argv + i) != NULL)
+ add_pack_file(*(argv + i++));
+
+ if (local_packs == NULL)
+ die("Zero packs found!\n");
+
+ load_all_objects();
+
+ cmp_local_packs();
+ if (alt_odb)
+ scan_alt_odb_packs();
+
+ minimize(&min);
+
+ if (verbose) {
+ fprintf(stderr, "There are %lu packs available in alt-odbs.\n",
+ (unsigned long)pack_list_size(altodb_packs));
+ fprintf(stderr, "The smallest (bytewise) set of packs is:\n");
+ pl = min;
+ while (pl) {
+ fprintf(stderr, "\t%s\n", pl->pack->pack_name);
+ pl = pl->next;
+ }
+ fprintf(stderr, "containing %lu duplicate objects "
+ "with a total size of %lukb.\n",
+ (unsigned long)get_pack_redundancy(min),
+ (unsigned long)pack_set_bytecount(min)/1024);
+ fprintf(stderr, "A total of %lu unique objects were considered.\n",
+ (unsigned long)all_objects->size);
+ fprintf(stderr, "Redundant packs (with indexes):\n");
+ }
+ pl = red = pack_list_difference(local_packs, min);
+ while (pl) {
+ printf("%s\n%s\n",
+ sha1_pack_index_name(pl->pack->sha1),
+ pl->pack->pack_name);
+ pl = pl->next;
+ }
+
+ return 0;
+}
diff --git a/pack.h b/pack.h
new file mode 100644
index 000000000..657deaa3f
--- /dev/null
+++ b/pack.h
@@ -0,0 +1,32 @@
+#ifndef PACK_H
+#define PACK_H
+
+/*
+ * The packed object type is stored in 3 bits.
+ * The type value 0 is a reserved prefix if ever there is more than 7
+ * object types, or any future format extensions.
+ */
+enum object_type {
+ OBJ_EXT = 0,
+ OBJ_COMMIT = 1,
+ OBJ_TREE = 2,
+ OBJ_BLOB = 3,
+ OBJ_TAG = 4,
+ /* 5/6 for future expansion */
+ OBJ_DELTA = 7,
+};
+
+/*
+ * Packed object header
+ */
+#define PACK_SIGNATURE 0x5041434b /* "PACK" */
+#define PACK_VERSION 2
+struct pack_header {
+ unsigned int hdr_signature;
+ unsigned int hdr_version;
+ unsigned int hdr_entries;
+};
+
+extern int verify_pack(struct packed_git *, int);
+
+#endif
diff --git a/patch-delta.c b/patch-delta.c
new file mode 100644
index 000000000..98c27beb2
--- /dev/null
+++ b/patch-delta.c
@@ -0,0 +1,73 @@
+/*
+ * patch-delta.c:
+ * recreate a buffer from a source and the delta produced by diff-delta.c
+ *
+ * (C) 2005 Nicolas Pitre <nico@cam.org>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "delta.h"
+
+void *patch_delta(void *src_buf, unsigned long src_size,
+ void *delta_buf, unsigned long delta_size,
+ unsigned long *dst_size)
+{
+ const unsigned char *data, *top;
+ unsigned char *dst_buf, *out, cmd;
+ unsigned long size;
+
+ if (delta_size < DELTA_SIZE_MIN)
+ return NULL;
+
+ data = delta_buf;
+ top = delta_buf + delta_size;
+
+ /* make sure the orig file size matches what we expect */
+ size = get_delta_hdr_size(&data);
+ if (size != src_size)
+ return NULL;
+
+ /* now the result size */
+ size = get_delta_hdr_size(&data);
+ dst_buf = malloc(size + 1);
+ if (!dst_buf)
+ return NULL;
+ dst_buf[size] = 0;
+
+ out = dst_buf;
+ while (data < top) {
+ cmd = *data++;
+ if (cmd & 0x80) {
+ unsigned long cp_off = 0, cp_size = 0;
+ const unsigned char *buf;
+ if (cmd & 0x01) cp_off = *data++;
+ if (cmd & 0x02) cp_off |= (*data++ << 8);
+ if (cmd & 0x04) cp_off |= (*data++ << 16);
+ if (cmd & 0x08) cp_off |= (*data++ << 24);
+ if (cmd & 0x10) cp_size = *data++;
+ if (cmd & 0x20) cp_size |= (*data++ << 8);
+ if (cp_size == 0) cp_size = 0x10000;
+ buf = (cmd & 0x40) ? dst_buf : src_buf;
+ memcpy(out, buf + cp_off, cp_size);
+ out += cp_size;
+ } else {
+ memcpy(out, data, cmd);
+ out += cmd;
+ data += cmd;
+ }
+ }
+
+ /* sanity check */
+ if (data != top || out - dst_buf != size) {
+ free(dst_buf);
+ return NULL;
+ }
+
+ *dst_size = size;
+ return dst_buf;
+}
diff --git a/patch-id.c b/patch-id.c
new file mode 100644
index 000000000..edbc4aa3e
--- /dev/null
+++ b/patch-id.c
@@ -0,0 +1,82 @@
+#include "cache.h"
+
+static void flush_current_id(int patchlen, unsigned char *id, SHA_CTX *c)
+{
+ unsigned char result[20];
+ char name[50];
+
+ if (!patchlen)
+ return;
+
+ SHA1_Final(result, c);
+ memcpy(name, sha1_to_hex(id), 41);
+ printf("%s %s\n", sha1_to_hex(result), name);
+ SHA1_Init(c);
+}
+
+static int remove_space(char *line)
+{
+ char *src = line;
+ char *dst = line;
+ unsigned char c;
+
+ while ((c = *src++) != '\0') {
+ if (!isspace(c))
+ *dst++ = c;
+ }
+ return dst - line;
+}
+
+static void generate_id_list(void)
+{
+ static unsigned char sha1[20];
+ static char line[1000];
+ SHA_CTX ctx;
+ int patchlen = 0;
+
+ SHA1_Init(&ctx);
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ unsigned char n[20];
+ char *p = line;
+ int len;
+
+ if (!memcmp(line, "diff-tree ", 10))
+ p += 10;
+
+ if (!get_sha1_hex(p, n)) {
+ flush_current_id(patchlen, sha1, &ctx);
+ memcpy(sha1, n, 20);
+ patchlen = 0;
+ continue;
+ }
+
+ /* Ignore commit comments */
+ if (!patchlen && memcmp(line, "diff ", 5))
+ continue;
+
+ /* Ignore git-diff index header */
+ if (!memcmp(line, "index ", 6))
+ continue;
+
+ /* Ignore line numbers when computing the SHA1 of the patch */
+ if (!memcmp(line, "@@ -", 4))
+ continue;
+
+ /* Compute the sha without whitespace */
+ len = remove_space(line);
+ patchlen += len;
+ SHA1_Update(&ctx, line, len);
+ }
+ flush_current_id(patchlen, sha1, &ctx);
+}
+
+static const char patch_id_usage[] = "git-patch-id < patch";
+
+int main(int argc, char **argv)
+{
+ if (argc != 1)
+ usage(patch_id_usage);
+
+ generate_id_list();
+ return 0;
+}
diff --git a/path.c b/path.c
new file mode 100644
index 000000000..495d17ca4
--- /dev/null
+++ b/path.c
@@ -0,0 +1,91 @@
+/*
+ * I'm tired of doing "vsnprintf()" etc just to open a
+ * file, so here's a "return static buffer with printf"
+ * interface for paths.
+ *
+ * It's obviously not thread-safe. Sue me. But it's quite
+ * useful for doing things like
+ *
+ * f = open(mkpath("%s/%s.git", base, name), O_RDONLY);
+ *
+ * which is what it's designed for.
+ */
+#include "cache.h"
+
+static char pathname[PATH_MAX];
+static char bad_path[] = "/bad-path/";
+
+static char *cleanup_path(char *path)
+{
+ /* Clean it up */
+ if (!memcmp(path, "./", 2)) {
+ path += 2;
+ while (*path == '/')
+ path++;
+ }
+ return path;
+}
+
+char *mkpath(const char *fmt, ...)
+{
+ va_list args;
+ unsigned len;
+
+ va_start(args, fmt);
+ len = vsnprintf(pathname, PATH_MAX, fmt, args);
+ va_end(args);
+ if (len >= PATH_MAX)
+ return bad_path;
+ return cleanup_path(pathname);
+}
+
+char *git_path(const char *fmt, ...)
+{
+ const char *git_dir = get_git_dir();
+ va_list args;
+ unsigned len;
+
+ len = strlen(git_dir);
+ if (len > PATH_MAX-100)
+ return bad_path;
+ memcpy(pathname, git_dir, len);
+ if (len && git_dir[len-1] != '/')
+ pathname[len++] = '/';
+ va_start(args, fmt);
+ len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ va_end(args);
+ if (len >= PATH_MAX)
+ return bad_path;
+ return cleanup_path(pathname);
+}
+
+
+/* git_mkstemp() - create tmp file honoring TMPDIR variable */
+int git_mkstemp(char *path, size_t len, const char *template)
+{
+ char *env, *pch = path;
+
+ if ((env = getenv("TMPDIR")) == NULL) {
+ strcpy(pch, "/tmp/");
+ len -= 5;
+ pch += 5;
+ } else {
+ size_t n = snprintf(pch, len, "%s/", env);
+
+ len -= n;
+ pch += n;
+ }
+
+ safe_strncpy(pch, template, len);
+
+ return mkstemp(path);
+}
+
+
+char *safe_strncpy(char *dest, const char *src, size_t n)
+{
+ strncpy(dest, src, n);
+ dest[n - 1] = '\0';
+
+ return dest;
+}
diff --git a/peek-remote.c b/peek-remote.c
new file mode 100644
index 000000000..ee49bf3b7
--- /dev/null
+++ b/peek-remote.c
@@ -0,0 +1,55 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static const char peek_remote_usage[] =
+"git-peek-remote [--exec=upload-pack] [host:]directory";
+static const char *exec = "git-upload-pack";
+
+static int peek_remote(int fd[2])
+{
+ struct ref *ref;
+
+ get_remote_heads(fd[0], &ref, 0, NULL, 0);
+ packet_flush(fd[1]);
+
+ while (ref) {
+ printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name);
+ ref = ref->next;
+ }
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int i, ret;
+ char *dest = NULL;
+ int fd[2];
+ pid_t pid;
+
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strncmp("--exec=", arg, 7))
+ exec = arg + 7;
+ else
+ usage(peek_remote_usage);
+ continue;
+ }
+ dest = arg;
+ break;
+ }
+ if (!dest || i != argc - 1)
+ usage(peek_remote_usage);
+
+ pid = git_connect(fd, dest, exec);
+ if (pid < 0)
+ return 1;
+ ret = peek_remote(fd);
+ close(fd[0]);
+ close(fd[1]);
+ finish_connect(pid);
+ return ret;
+}
diff --git a/pkt-line.c b/pkt-line.c
new file mode 100644
index 000000000..69473046b
--- /dev/null
+++ b/pkt-line.c
@@ -0,0 +1,117 @@
+#include "cache.h"
+#include "pkt-line.h"
+
+/*
+ * Write a packetized stream, where each line is preceded by
+ * its length (including the header) as a 4-byte hex number.
+ * A length of 'zero' means end of stream (and a length of 1-3
+ * would be an error).
+ *
+ * This is all pretty stupid, but we use this packetized line
+ * format to make a streaming format possible without ever
+ * over-running the read buffers. That way we'll never read
+ * into what might be the pack data (which should go to another
+ * process entirely).
+ *
+ * The writing side could use stdio, but since the reading
+ * side can't, we stay with pure read/write interfaces.
+ */
+static void safe_write(int fd, const void *buf, unsigned n)
+{
+ while (n) {
+ int ret = write(fd, buf, n);
+ if (ret > 0) {
+ buf += ret;
+ n -= ret;
+ continue;
+ }
+ if (!ret)
+ die("write error (disk full?)");
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ die("write error (%s)", strerror(errno));
+ }
+}
+
+/*
+ * If we buffered things up above (we don't, but we should),
+ * we'd flush it here
+ */
+void packet_flush(int fd)
+{
+ safe_write(fd, "0000", 4);
+}
+
+#define hex(a) (hexchar[(a) & 15])
+void packet_write(int fd, const char *fmt, ...)
+{
+ static char buffer[1000];
+ static char hexchar[] = "0123456789abcdef";
+ va_list args;
+ unsigned n;
+
+ va_start(args, fmt);
+ n = vsnprintf(buffer + 4, sizeof(buffer) - 4, fmt, args);
+ va_end(args);
+ if (n >= sizeof(buffer)-4)
+ die("protocol error: impossibly long line");
+ n += 4;
+ buffer[0] = hex(n >> 12);
+ buffer[1] = hex(n >> 8);
+ buffer[2] = hex(n >> 4);
+ buffer[3] = hex(n);
+ safe_write(fd, buffer, n);
+}
+
+static void safe_read(int fd, void *buffer, unsigned size)
+{
+ int n = 0;
+
+ while (n < size) {
+ int ret = read(fd, buffer + n, size - n);
+ if (ret < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ die("read error (%s)", strerror(errno));
+ }
+ if (!ret)
+ die("unexpected EOF");
+ n += ret;
+ }
+}
+
+int packet_read_line(int fd, char *buffer, unsigned size)
+{
+ int n;
+ unsigned len;
+ char linelen[4];
+
+ safe_read(fd, linelen, 4);
+
+ len = 0;
+ for (n = 0; n < 4; n++) {
+ unsigned char c = linelen[n];
+ len <<= 4;
+ if (c >= '0' && c <= '9') {
+ len += c - '0';
+ continue;
+ }
+ if (c >= 'a' && c <= 'f') {
+ len += c - 'a' + 10;
+ continue;
+ }
+ if (c >= 'A' && c <= 'F') {
+ len += c - 'A' + 10;
+ continue;
+ }
+ die("protocol error: bad line length character");
+ }
+ if (!len)
+ return 0;
+ len -= 4;
+ if (len >= size)
+ die("protocol error: bad line length %d", len);
+ safe_read(fd, buffer, len);
+ buffer[len] = 0;
+ return len;
+}
diff --git a/pkt-line.h b/pkt-line.h
new file mode 100644
index 000000000..51d0cbe21
--- /dev/null
+++ b/pkt-line.h
@@ -0,0 +1,12 @@
+#ifndef PKTLINE_H
+#define PKTLINE_H
+
+/*
+ * Silly packetized line writing interface
+ */
+void packet_flush(int fd);
+void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
+
+int packet_read_line(int fd, char *buffer, unsigned size);
+
+#endif
diff --git a/ppc/sha1.c b/ppc/sha1.c
new file mode 100644
index 000000000..5ba4fc525
--- /dev/null
+++ b/ppc/sha1.c
@@ -0,0 +1,72 @@
+/*
+ * SHA-1 implementation.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ *
+ * This version assumes we are running on a big-endian machine.
+ * It calls an external sha1_core() to process blocks of 64 bytes.
+ */
+#include <stdio.h>
+#include <string.h>
+#include "sha1.h"
+
+extern void sha1_core(uint32_t *hash, const unsigned char *p,
+ unsigned int nblocks);
+
+int SHA1_Init(SHA_CTX *c)
+{
+ c->hash[0] = 0x67452301;
+ c->hash[1] = 0xEFCDAB89;
+ c->hash[2] = 0x98BADCFE;
+ c->hash[3] = 0x10325476;
+ c->hash[4] = 0xC3D2E1F0;
+ c->len = 0;
+ c->cnt = 0;
+ return 0;
+}
+
+int SHA1_Update(SHA_CTX *c, const void *ptr, unsigned long n)
+{
+ unsigned long nb;
+ const unsigned char *p = ptr;
+
+ c->len += n << 3;
+ while (n != 0) {
+ if (c->cnt || n < 64) {
+ nb = 64 - c->cnt;
+ if (nb > n)
+ nb = n;
+ memcpy(&c->buf.b[c->cnt], p, nb);
+ if ((c->cnt += nb) == 64) {
+ sha1_core(c->hash, c->buf.b, 1);
+ c->cnt = 0;
+ }
+ } else {
+ nb = n >> 6;
+ sha1_core(c->hash, p, nb);
+ nb <<= 6;
+ }
+ n -= nb;
+ p += nb;
+ }
+ return 0;
+}
+
+int SHA1_Final(unsigned char *hash, SHA_CTX *c)
+{
+ unsigned int cnt = c->cnt;
+
+ c->buf.b[cnt++] = 0x80;
+ if (cnt > 56) {
+ if (cnt < 64)
+ memset(&c->buf.b[cnt], 0, 64 - cnt);
+ sha1_core(c->hash, c->buf.b, 1);
+ cnt = 0;
+ }
+ if (cnt < 56)
+ memset(&c->buf.b[cnt], 0, 56 - cnt);
+ c->buf.l[7] = c->len;
+ sha1_core(c->hash, c->buf.b, 1);
+ memcpy(hash, c->hash, 20);
+ return 0;
+}
diff --git a/ppc/sha1.h b/ppc/sha1.h
new file mode 100644
index 000000000..c3c51aa4d
--- /dev/null
+++ b/ppc/sha1.h
@@ -0,0 +1,20 @@
+/*
+ * SHA-1 implementation.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ */
+#include <stdint.h>
+
+typedef struct sha_context {
+ uint32_t hash[5];
+ uint32_t cnt;
+ uint64_t len;
+ union {
+ unsigned char b[64];
+ uint64_t l[8];
+ } buf;
+} SHA_CTX;
+
+int SHA1_Init(SHA_CTX *c);
+int SHA1_Update(SHA_CTX *c, const void *p, unsigned long n);
+int SHA1_Final(unsigned char *hash, SHA_CTX *c);
diff --git a/ppc/sha1ppc.S b/ppc/sha1ppc.S
new file mode 100644
index 000000000..e85611a4e
--- /dev/null
+++ b/ppc/sha1ppc.S
@@ -0,0 +1,185 @@
+/*
+ * SHA-1 implementation for PowerPC.
+ *
+ * Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+ */
+#define FS 80
+
+/*
+ * We roll the registers for T, A, B, C, D, E around on each
+ * iteration; T on iteration t is A on iteration t+1, and so on.
+ * We use registers 7 - 12 for this.
+ */
+#define RT(t) ((((t)+5)%6)+7)
+#define RA(t) ((((t)+4)%6)+7)
+#define RB(t) ((((t)+3)%6)+7)
+#define RC(t) ((((t)+2)%6)+7)
+#define RD(t) ((((t)+1)%6)+7)
+#define RE(t) ((((t)+0)%6)+7)
+
+/* We use registers 16 - 31 for the W values */
+#define W(t) (((t)%16)+16)
+
+#define STEPD0(t) \
+ and %r6,RB(t),RC(t); \
+ andc %r0,RD(t),RB(t); \
+ rotlwi RT(t),RA(t),5; \
+ rotlwi RB(t),RB(t),30; \
+ or %r6,%r6,%r0; \
+ add %r0,RE(t),%r15; \
+ add RT(t),RT(t),%r6; \
+ add %r0,%r0,W(t); \
+ add RT(t),RT(t),%r0
+
+#define STEPD1(t) \
+ xor %r6,RB(t),RC(t); \
+ rotlwi RT(t),RA(t),5; \
+ rotlwi RB(t),RB(t),30; \
+ xor %r6,%r6,RD(t); \
+ add %r0,RE(t),%r15; \
+ add RT(t),RT(t),%r6; \
+ add %r0,%r0,W(t); \
+ add RT(t),RT(t),%r0
+
+#define STEPD2(t) \
+ and %r6,RB(t),RC(t); \
+ and %r0,RB(t),RD(t); \
+ rotlwi RT(t),RA(t),5; \
+ rotlwi RB(t),RB(t),30; \
+ or %r6,%r6,%r0; \
+ and %r0,RC(t),RD(t); \
+ or %r6,%r6,%r0; \
+ add %r0,RE(t),%r15; \
+ add RT(t),RT(t),%r6; \
+ add %r0,%r0,W(t); \
+ add RT(t),RT(t),%r0
+
+#define LOADW(t) \
+ lwz W(t),(t)*4(%r4)
+
+#define UPDATEW(t) \
+ xor %r0,W((t)-3),W((t)-8); \
+ xor W(t),W((t)-16),W((t)-14); \
+ xor W(t),W(t),%r0; \
+ rotlwi W(t),W(t),1
+
+#define STEP0LD4(t) \
+ STEPD0(t); LOADW((t)+4); \
+ STEPD0((t)+1); LOADW((t)+5); \
+ STEPD0((t)+2); LOADW((t)+6); \
+ STEPD0((t)+3); LOADW((t)+7)
+
+#define STEPUP4(t, fn) \
+ STEP##fn(t); UPDATEW((t)+4); \
+ STEP##fn((t)+1); UPDATEW((t)+5); \
+ STEP##fn((t)+2); UPDATEW((t)+6); \
+ STEP##fn((t)+3); UPDATEW((t)+7)
+
+#define STEPUP20(t, fn) \
+ STEPUP4(t, fn); \
+ STEPUP4((t)+4, fn); \
+ STEPUP4((t)+8, fn); \
+ STEPUP4((t)+12, fn); \
+ STEPUP4((t)+16, fn)
+
+ .globl sha1_core
+sha1_core:
+ stwu %r1,-FS(%r1)
+ stw %r15,FS-68(%r1)
+ stw %r16,FS-64(%r1)
+ stw %r17,FS-60(%r1)
+ stw %r18,FS-56(%r1)
+ stw %r19,FS-52(%r1)
+ stw %r20,FS-48(%r1)
+ stw %r21,FS-44(%r1)
+ stw %r22,FS-40(%r1)
+ stw %r23,FS-36(%r1)
+ stw %r24,FS-32(%r1)
+ stw %r25,FS-28(%r1)
+ stw %r26,FS-24(%r1)
+ stw %r27,FS-20(%r1)
+ stw %r28,FS-16(%r1)
+ stw %r29,FS-12(%r1)
+ stw %r30,FS-8(%r1)
+ stw %r31,FS-4(%r1)
+
+ /* Load up A - E */
+ lwz RA(0),0(%r3) /* A */
+ lwz RB(0),4(%r3) /* B */
+ lwz RC(0),8(%r3) /* C */
+ lwz RD(0),12(%r3) /* D */
+ lwz RE(0),16(%r3) /* E */
+
+ mtctr %r5
+
+1: LOADW(0)
+ LOADW(1)
+ LOADW(2)
+ LOADW(3)
+
+ lis %r15,0x5a82 /* K0-19 */
+ ori %r15,%r15,0x7999
+ STEP0LD4(0)
+ STEP0LD4(4)
+ STEP0LD4(8)
+ STEPUP4(12, D0)
+ STEPUP4(16, D0)
+
+ lis %r15,0x6ed9 /* K20-39 */
+ ori %r15,%r15,0xeba1
+ STEPUP20(20, D1)
+
+ lis %r15,0x8f1b /* K40-59 */
+ ori %r15,%r15,0xbcdc
+ STEPUP20(40, D2)
+
+ lis %r15,0xca62 /* K60-79 */
+ ori %r15,%r15,0xc1d6
+ STEPUP4(60, D1)
+ STEPUP4(64, D1)
+ STEPUP4(68, D1)
+ STEPUP4(72, D1)
+ STEPD1(76)
+ STEPD1(77)
+ STEPD1(78)
+ STEPD1(79)
+
+ lwz %r20,16(%r3)
+ lwz %r19,12(%r3)
+ lwz %r18,8(%r3)
+ lwz %r17,4(%r3)
+ lwz %r16,0(%r3)
+ add %r20,RE(80),%r20
+ add RD(0),RD(80),%r19
+ add RC(0),RC(80),%r18
+ add RB(0),RB(80),%r17
+ add RA(0),RA(80),%r16
+ mr RE(0),%r20
+ stw RA(0),0(%r3)
+ stw RB(0),4(%r3)
+ stw RC(0),8(%r3)
+ stw RD(0),12(%r3)
+ stw RE(0),16(%r3)
+
+ addi %r4,%r4,64
+ bdnz 1b
+
+ lwz %r15,FS-68(%r1)
+ lwz %r16,FS-64(%r1)
+ lwz %r17,FS-60(%r1)
+ lwz %r18,FS-56(%r1)
+ lwz %r19,FS-52(%r1)
+ lwz %r20,FS-48(%r1)
+ lwz %r21,FS-44(%r1)
+ lwz %r22,FS-40(%r1)
+ lwz %r23,FS-36(%r1)
+ lwz %r24,FS-32(%r1)
+ lwz %r25,FS-28(%r1)
+ lwz %r26,FS-24(%r1)
+ lwz %r27,FS-20(%r1)
+ lwz %r28,FS-16(%r1)
+ lwz %r29,FS-12(%r1)
+ lwz %r30,FS-8(%r1)
+ lwz %r31,FS-4(%r1)
+ addi %r1,%r1,FS
+ blr
diff --git a/prune-packed.c b/prune-packed.c
new file mode 100644
index 000000000..26123f7f6
--- /dev/null
+++ b/prune-packed.c
@@ -0,0 +1,77 @@
+#include "cache.h"
+
+static const char prune_packed_usage[] =
+"git-prune-packed [-n]";
+
+static int dryrun;
+
+static void prune_dir(int i, DIR *dir, char *pathname, int len)
+{
+ struct dirent *de;
+ char hex[40];
+
+ sprintf(hex, "%02x", i);
+ while ((de = readdir(dir)) != NULL) {
+ unsigned char sha1[20];
+ if (strlen(de->d_name) != 38)
+ continue;
+ memcpy(hex+2, de->d_name, 38);
+ if (get_sha1_hex(hex, sha1))
+ continue;
+ if (!has_sha1_pack(sha1))
+ continue;
+ memcpy(pathname + len, de->d_name, 38);
+ if (dryrun)
+ printf("rm -f %s\n", pathname);
+ else if (unlink(pathname) < 0)
+ error("unable to unlink %s", pathname);
+ }
+ pathname[len] = 0;
+ rmdir(pathname);
+}
+
+static void prune_packed_objects(void)
+{
+ int i;
+ static char pathname[PATH_MAX];
+ const char *dir = get_object_directory();
+ int len = strlen(dir);
+
+ if (len > PATH_MAX - 42)
+ die("impossible object directory");
+ memcpy(pathname, dir, len);
+ if (len && pathname[len-1] != '/')
+ pathname[len++] = '/';
+ for (i = 0; i < 256; i++) {
+ DIR *d;
+
+ sprintf(pathname + len, "%02x/", i);
+ d = opendir(pathname);
+ if (!d)
+ continue;
+ prune_dir(i, d, pathname, len + 3);
+ closedir(d);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "-n"))
+ dryrun = 1;
+ else
+ usage(prune_packed_usage);
+ continue;
+ }
+ /* Handle arguments here .. */
+ usage(prune_packed_usage);
+ }
+ sync();
+ prune_packed_objects();
+ return 0;
+}
diff --git a/quote.c b/quote.c
new file mode 100644
index 000000000..e662a7da7
--- /dev/null
+++ b/quote.c
@@ -0,0 +1,257 @@
+#include "cache.h"
+#include "quote.h"
+
+/* Help to copy the thing properly quoted for the shell safety.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
+ *
+ * E.g.
+ * original sq_quote result
+ * name ==> name ==> 'name'
+ * a b ==> a b ==> 'a b'
+ * a'b ==> a'\''b ==> 'a'\''b'
+ * a!b ==> a'\!'b ==> 'a'\!'b'
+ */
+#undef EMIT
+#define EMIT(x) ( (++len < n) && (*bp++ = (x)) )
+
+static inline int need_bs_quote(char c)
+{
+ return (c == '\'' || c == '!');
+}
+
+size_t sq_quote_buf(char *dst, size_t n, const char *src)
+{
+ char c;
+ char *bp = dst;
+ size_t len = 0;
+
+ EMIT('\'');
+ while ((c = *src++)) {
+ if (need_bs_quote(c)) {
+ EMIT('\'');
+ EMIT('\\');
+ EMIT(c);
+ EMIT('\'');
+ } else {
+ EMIT(c);
+ }
+ }
+ EMIT('\'');
+
+ if ( n )
+ *bp = 0;
+
+ return len;
+}
+
+char *sq_quote(const char *src)
+{
+ char *buf;
+ size_t cnt;
+
+ cnt = sq_quote_buf(NULL, 0, src) + 1;
+ buf = xmalloc(cnt);
+ sq_quote_buf(buf, cnt, src);
+
+ return buf;
+}
+
+char *sq_dequote(char *arg)
+{
+ char *dst = arg;
+ char *src = arg;
+ char c;
+
+ if (*src != '\'')
+ return NULL;
+ for (;;) {
+ c = *++src;
+ if (!c)
+ return NULL;
+ if (c != '\'') {
+ *dst++ = c;
+ continue;
+ }
+ /* We stepped out of sq */
+ switch (*++src) {
+ case '\0':
+ *dst = 0;
+ return arg;
+ case '\\':
+ c = *++src;
+ if (need_bs_quote(c) && *++src == '\'') {
+ *dst++ = c;
+ continue;
+ }
+ /* Fallthrough */
+ default:
+ return NULL;
+ }
+ }
+}
+
+/*
+ * C-style name quoting.
+ *
+ * Does one of three things:
+ *
+ * (1) if outbuf and outfp are both NULL, inspect the input name and
+ * counts the number of bytes that are needed to hold c_style
+ * quoted version of name, counting the double quotes around
+ * it but not terminating NUL, and returns it. However, if name
+ * does not need c_style quoting, it returns 0.
+ *
+ * (2) if outbuf is not NULL, it must point at a buffer large enough
+ * to hold the c_style quoted version of name, enclosing double
+ * quotes, and terminating NUL. Fills outbuf with c_style quoted
+ * version of name enclosed in double-quote pair. Return value
+ * is undefined.
+ *
+ * (3) if outfp is not NULL, outputs c_style quoted version of name,
+ * but not enclosed in double-quote pair. Return value is undefined.
+ */
+
+int quote_c_style(const char *name, char *outbuf, FILE *outfp, int no_dq)
+{
+#undef EMIT
+#define EMIT(c) \
+ (outbuf ? (*outbuf++ = (c)) : outfp ? fputc(c, outfp) : (count++))
+
+#define EMITQ() EMIT('\\')
+
+ const char *sp;
+ int ch, count = 0, needquote = 0;
+
+ if (!no_dq)
+ EMIT('"');
+ for (sp = name; (ch = *sp++); ) {
+
+ if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
+ (ch == 0177)) {
+ needquote = 1;
+ switch (ch) {
+ case '\a': EMITQ(); ch = 'a'; break;
+ case '\b': EMITQ(); ch = 'b'; break;
+ case '\f': EMITQ(); ch = 'f'; break;
+ case '\n': EMITQ(); ch = 'n'; break;
+ case '\r': EMITQ(); ch = 'r'; break;
+ case '\t': EMITQ(); ch = 't'; break;
+ case '\v': EMITQ(); ch = 'v'; break;
+
+ case '\\': /* fallthru */
+ case '"': EMITQ(); break;
+ case ' ':
+ break;
+ default:
+ /* octal */
+ EMITQ();
+ EMIT(((ch >> 6) & 03) + '0');
+ EMIT(((ch >> 3) & 07) + '0');
+ ch = (ch & 07) + '0';
+ break;
+ }
+ }
+ EMIT(ch);
+ }
+ if (!no_dq)
+ EMIT('"');
+ if (outbuf)
+ *outbuf = 0;
+
+ return needquote ? count : 0;
+}
+
+/*
+ * C-style name unquoting.
+ *
+ * Quoted should point at the opening double quote. Returns
+ * an allocated memory that holds unquoted name, which the caller
+ * should free when done. Updates endp pointer to point at
+ * one past the ending double quote if given.
+ */
+
+char *unquote_c_style(const char *quoted, const char **endp)
+{
+ const char *sp;
+ char *name = NULL, *outp = NULL;
+ int count = 0, ch, ac;
+
+#undef EMIT
+#define EMIT(c) (outp ? (*outp++ = (c)) : (count++))
+
+ if (*quoted++ != '"')
+ return NULL;
+
+ while (1) {
+ /* first pass counts and allocates, second pass fills */
+ for (sp = quoted; (ch = *sp++) != '"'; ) {
+ if (ch == '\\') {
+ switch (ch = *sp++) {
+ case 'a': ch = '\a'; break;
+ case 'b': ch = '\b'; break;
+ case 'f': ch = '\f'; break;
+ case 'n': ch = '\n'; break;
+ case 'r': ch = '\r'; break;
+ case 't': ch = '\t'; break;
+ case 'v': ch = '\v'; break;
+
+ case '\\': case '"':
+ break; /* verbatim */
+
+ case '0'...'7':
+ /* octal */
+ ac = ((ch - '0') << 6);
+ if ((ch = *sp++) < '0' || '7' < ch)
+ return NULL;
+ ac |= ((ch - '0') << 3);
+ if ((ch = *sp++) < '0' || '7' < ch)
+ return NULL;
+ ac |= (ch - '0');
+ ch = ac;
+ break;
+ default:
+ return NULL; /* malformed */
+ }
+ }
+ EMIT(ch);
+ }
+
+ if (name) {
+ *outp = 0;
+ if (endp)
+ *endp = sp;
+ return name;
+ }
+ outp = name = xmalloc(count + 1);
+ }
+}
+
+void write_name_quoted(const char *prefix, const char *name,
+ int quote, FILE *out)
+{
+ int needquote;
+
+ if (!quote) {
+ no_quote:
+ if (prefix && prefix[0])
+ fputs(prefix, out);
+ fputs(name, out);
+ return;
+ }
+
+ needquote = 0;
+ if (prefix && prefix[0])
+ needquote = quote_c_style(prefix, NULL, NULL, 0);
+ if (!needquote)
+ needquote = quote_c_style(name, NULL, NULL, 0);
+ if (needquote) {
+ fputc('"', out);
+ if (prefix && prefix[0])
+ quote_c_style(prefix, NULL, out, 1);
+ quote_c_style(name, NULL, out, 1);
+ fputc('"', out);
+ }
+ else
+ goto no_quote;
+}
diff --git a/quote.h b/quote.h
new file mode 100644
index 000000000..2486e6e68
--- /dev/null
+++ b/quote.h
@@ -0,0 +1,47 @@
+#ifndef QUOTE_H
+#define QUOTE_H
+
+#include <stddef.h>
+#include <stdio.h>
+
+/* Help to copy the thing properly quoted for the shell safety.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
+ * single quote pair.
+ *
+ * For example, if you are passing the result to system() as an
+ * argument:
+ *
+ * sprintf(cmd, "foobar %s %s", sq_quote(arg0), sq_quote(arg1))
+ *
+ * would be appropriate. If the system() is going to call ssh to
+ * run the command on the other side:
+ *
+ * sprintf(cmd, "git-diff-tree %s %s", sq_quote(arg0), sq_quote(arg1));
+ * sprintf(rcmd, "ssh %s %s", sq_quote(host), sq_quote(cmd));
+ *
+ * Note that the above examples leak memory! Remember to free result from
+ * sq_quote() in a real application.
+ *
+ * sq_quote_buf() writes to an existing buffer of specified size; it
+ * will return the number of characters that would have been written
+ * excluding the final null regardless of the buffer size.
+ */
+
+extern char *sq_quote(const char *src);
+extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
+
+/* This unwraps what sq_quote() produces in place, but returns
+ * NULL if the input does not look like what sq_quote would have
+ * produced.
+ */
+extern char *sq_dequote(char *);
+
+extern int quote_c_style(const char *name, char *outbuf, FILE *outfp,
+ int nodq);
+extern char *unquote_c_style(const char *quoted, const char **endp);
+
+extern void write_name_quoted(const char *prefix, const char *name,
+ int quote, FILE *out);
+
+#endif
diff --git a/read-cache.c b/read-cache.c
new file mode 100644
index 000000000..693273620
--- /dev/null
+++ b/read-cache.c
@@ -0,0 +1,591 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+struct cache_entry **active_cache = NULL;
+unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
+
+/*
+ * This only updates the "non-critical" parts of the directory
+ * cache, ie the parts that aren't tracked by GIT, and only used
+ * to validate the cache.
+ */
+void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
+{
+ ce->ce_ctime.sec = htonl(st->st_ctime);
+ ce->ce_mtime.sec = htonl(st->st_mtime);
+#ifdef USE_NSEC
+ ce->ce_ctime.nsec = htonl(st->st_ctim.tv_nsec);
+ ce->ce_mtime.nsec = htonl(st->st_mtim.tv_nsec);
+#endif
+ ce->ce_dev = htonl(st->st_dev);
+ ce->ce_ino = htonl(st->st_ino);
+ ce->ce_uid = htonl(st->st_uid);
+ ce->ce_gid = htonl(st->st_gid);
+ ce->ce_size = htonl(st->st_size);
+}
+
+int ce_match_stat(struct cache_entry *ce, struct stat *st)
+{
+ unsigned int changed = 0;
+
+ switch (ntohl(ce->ce_mode) & S_IFMT) {
+ case S_IFREG:
+ changed |= !S_ISREG(st->st_mode) ? TYPE_CHANGED : 0;
+ /* We consider only the owner x bit to be relevant for
+ * "mode changes"
+ */
+ if (trust_executable_bit &&
+ (0100 & (ntohl(ce->ce_mode) ^ st->st_mode)))
+ changed |= MODE_CHANGED;
+ break;
+ case S_IFLNK:
+ changed |= !S_ISLNK(st->st_mode) ? TYPE_CHANGED : 0;
+ break;
+ default:
+ die("internal error: ce_mode is %o", ntohl(ce->ce_mode));
+ }
+ if (ce->ce_mtime.sec != htonl(st->st_mtime))
+ changed |= MTIME_CHANGED;
+ if (ce->ce_ctime.sec != htonl(st->st_ctime))
+ changed |= CTIME_CHANGED;
+
+#ifdef USE_NSEC
+ /*
+ * nsec seems unreliable - not all filesystems support it, so
+ * as long as it is in the inode cache you get right nsec
+ * but after it gets flushed, you get zero nsec.
+ */
+ if (ce->ce_mtime.nsec != htonl(st->st_mtim.tv_nsec))
+ changed |= MTIME_CHANGED;
+ if (ce->ce_ctime.nsec != htonl(st->st_ctim.tv_nsec))
+ changed |= CTIME_CHANGED;
+#endif
+
+ if (ce->ce_uid != htonl(st->st_uid) ||
+ ce->ce_gid != htonl(st->st_gid))
+ changed |= OWNER_CHANGED;
+ if (ce->ce_ino != htonl(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 (ce->ce_dev != htonl(st->st_dev))
+ changed |= INODE_CHANGED;
+#endif
+
+ if (ce->ce_size != htonl(st->st_size))
+ changed |= DATA_CHANGED;
+ return changed;
+}
+
+static int ce_compare_data(struct cache_entry *ce, struct stat *st)
+{
+ int match = -1;
+ int fd = open(ce->name, O_RDONLY);
+
+ if (fd >= 0) {
+ unsigned char sha1[20];
+ if (!index_fd(sha1, fd, st, 0, NULL))
+ match = memcmp(sha1, ce->sha1, 20);
+ close(fd);
+ }
+ return match;
+}
+
+static int ce_compare_link(struct cache_entry *ce, unsigned long expected_size)
+{
+ int match = -1;
+ char *target;
+ void *buffer;
+ unsigned long size;
+ char type[10];
+ int len;
+
+ target = xmalloc(expected_size);
+ len = readlink(ce->name, target, expected_size);
+ if (len != expected_size) {
+ free(target);
+ return -1;
+ }
+ buffer = read_sha1_file(ce->sha1, type, &size);
+ if (!buffer) {
+ free(target);
+ return -1;
+ }
+ if (size == expected_size)
+ match = memcmp(buffer, target, size);
+ free(buffer);
+ free(target);
+ return match;
+}
+
+int ce_modified(struct cache_entry *ce, struct stat *st)
+{
+ int changed;
+ changed = ce_match_stat(ce, st);
+ if (!changed)
+ return 0;
+
+ /*
+ * If the mode or type has changed, there's no point in trying
+ * to refresh the entry - it's not going to match
+ */
+ if (changed & (MODE_CHANGED | TYPE_CHANGED))
+ return changed;
+
+ /* Immediately after read-tree or update-index --cacheinfo,
+ * the length field is zero. For other cases the ce_size
+ * should match the SHA1 recorded in the index entry.
+ */
+ if ((changed & DATA_CHANGED) && ce->ce_size != htonl(0))
+ return changed;
+
+ switch (st->st_mode & S_IFMT) {
+ case S_IFREG:
+ if (ce_compare_data(ce, st))
+ return changed | DATA_CHANGED;
+ break;
+ case S_IFLNK:
+ if (ce_compare_link(ce, st->st_size))
+ return changed | DATA_CHANGED;
+ break;
+ default:
+ return changed | TYPE_CHANGED;
+ }
+ return 0;
+}
+
+int base_name_compare(const char *name1, int len1, int mode1,
+ const char *name2, int len2, int mode2)
+{
+ unsigned char c1, c2;
+ int len = len1 < len2 ? len1 : len2;
+ int cmp;
+
+ cmp = memcmp(name1, name2, len);
+ if (cmp)
+ return cmp;
+ c1 = name1[len];
+ c2 = name2[len];
+ if (!c1 && S_ISDIR(mode1))
+ c1 = '/';
+ if (!c2 && S_ISDIR(mode2))
+ c2 = '/';
+ return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+}
+
+int cache_name_compare(const char *name1, int flags1, const char *name2, int flags2)
+{
+ int len1 = flags1 & CE_NAMEMASK;
+ int len2 = flags2 & CE_NAMEMASK;
+ int len = len1 < len2 ? len1 : len2;
+ int cmp;
+
+ cmp = memcmp(name1, name2, len);
+ if (cmp)
+ return cmp;
+ if (len1 < len2)
+ return -1;
+ if (len1 > len2)
+ return 1;
+ if (flags1 < flags2)
+ return -1;
+ if (flags1 > flags2)
+ return 1;
+ return 0;
+}
+
+int cache_name_pos(const char *name, int namelen)
+{
+ int first, last;
+
+ first = 0;
+ last = active_nr;
+ while (last > first) {
+ int next = (last + first) >> 1;
+ struct cache_entry *ce = active_cache[next];
+ int cmp = cache_name_compare(name, namelen, ce->name, ntohs(ce->ce_flags));
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+ return -first-1;
+}
+
+/* Remove entry, return true if there are more entries to go.. */
+int remove_cache_entry_at(int pos)
+{
+ active_cache_changed = 1;
+ active_nr--;
+ if (pos >= active_nr)
+ return 0;
+ memmove(active_cache + pos, active_cache + pos + 1, (active_nr - pos) * sizeof(struct cache_entry *));
+ return 1;
+}
+
+int remove_file_from_cache(const char *path)
+{
+ int pos = cache_name_pos(path, strlen(path));
+ if (pos < 0)
+ pos = -pos-1;
+ while (pos < active_nr && !strcmp(active_cache[pos]->name, path))
+ remove_cache_entry_at(pos);
+ return 0;
+}
+
+int ce_same_name(struct cache_entry *a, struct cache_entry *b)
+{
+ int len = ce_namelen(a);
+ return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
+}
+
+int ce_path_match(const struct cache_entry *ce, const char **pathspec)
+{
+ const char *match, *name;
+ int len;
+
+ if (!pathspec)
+ return 1;
+
+ len = ce_namelen(ce);
+ name = ce->name;
+ while ((match = *pathspec++) != NULL) {
+ int matchlen = strlen(match);
+ if (matchlen > len)
+ continue;
+ if (memcmp(name, match, matchlen))
+ continue;
+ if (matchlen && name[matchlen-1] == '/')
+ return 1;
+ if (name[matchlen] == '/' || !name[matchlen])
+ return 1;
+ if (!matchlen)
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * Do we have another file that has the beginning components being a
+ * proper superset of the name we're trying to add?
+ */
+static int has_file_name(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+ int retval = 0;
+ int len = ce_namelen(ce);
+ int stage = ce_stage(ce);
+ const char *name = ce->name;
+
+ while (pos < active_nr) {
+ struct cache_entry *p = active_cache[pos++];
+
+ if (len >= ce_namelen(p))
+ break;
+ if (memcmp(name, p->name, len))
+ break;
+ if (ce_stage(p) != stage)
+ continue;
+ if (p->name[len] != '/')
+ continue;
+ retval = -1;
+ if (!ok_to_replace)
+ break;
+ remove_cache_entry_at(--pos);
+ }
+ return retval;
+}
+
+/*
+ * Do we have another file with a pathname that is a proper
+ * subset of the name we're trying to add?
+ */
+static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+ int retval = 0;
+ int stage = ce_stage(ce);
+ const char *name = ce->name;
+ const char *slash = name + ce_namelen(ce);
+
+ for (;;) {
+ int len;
+
+ for (;;) {
+ if (*--slash == '/')
+ break;
+ if (slash <= ce->name)
+ return retval;
+ }
+ len = slash - name;
+
+ pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage)));
+ if (pos >= 0) {
+ retval = -1;
+ if (ok_to_replace)
+ break;
+ remove_cache_entry_at(pos);
+ continue;
+ }
+
+ /*
+ * Trivial optimization: if we find an entry that
+ * already matches the sub-directory, then we know
+ * we're ok, and we can exit.
+ */
+ pos = -pos-1;
+ while (pos < active_nr) {
+ struct cache_entry *p = active_cache[pos];
+ if ((ce_namelen(p) <= len) ||
+ (p->name[len] != '/') ||
+ memcmp(p->name, name, len))
+ break; /* not our subdirectory */
+ if (ce_stage(p) == stage)
+ /* p is at the same stage as our entry, and
+ * is a subdirectory of what we are looking
+ * at, so we cannot have conflicts at our
+ * level or anything shorter.
+ */
+ return retval;
+ pos++;
+ }
+ }
+ return retval;
+}
+
+/* We may be in a situation where we already have path/file and path
+ * is being added, or we already have path and path/file is being
+ * added. Either one would result in a nonsense tree that has path
+ * twice when git-write-tree tries to write it out. Prevent it.
+ *
+ * If ok-to-replace is specified, we remove the conflicting entries
+ * from the cache so the caller should recompute the insert position.
+ * When this happens, we return non-zero.
+ */
+static int check_file_directory_conflict(const struct cache_entry *ce, int pos, int ok_to_replace)
+{
+ /*
+ * We check if the path is a sub-path of a subsequent pathname
+ * first, since removing those will not change the position
+ * in the array
+ */
+ int retval = has_file_name(ce, pos, ok_to_replace);
+ /*
+ * Then check if the path might have a clashing sub-directory
+ * before it.
+ */
+ return retval + has_dir_name(ce, pos, ok_to_replace);
+}
+
+int add_cache_entry(struct cache_entry *ce, int option)
+{
+ int pos;
+ int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
+ int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
+ int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
+ pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
+
+ /* existing match? Just replace it. */
+ if (pos >= 0) {
+ active_cache_changed = 1;
+ active_cache[pos] = ce;
+ return 0;
+ }
+ pos = -pos-1;
+
+ /*
+ * Inserting a merged entry ("stage 0") into the index
+ * will always replace all non-merged entries..
+ */
+ if (pos < active_nr && ce_stage(ce) == 0) {
+ while (ce_same_name(active_cache[pos], ce)) {
+ ok_to_add = 1;
+ if (!remove_cache_entry_at(pos))
+ break;
+ }
+ }
+
+ if (!ok_to_add)
+ return -1;
+
+ if (!skip_df_check &&
+ check_file_directory_conflict(ce, pos, ok_to_replace)) {
+ if (!ok_to_replace)
+ return -1;
+ pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
+ pos = -pos-1;
+ }
+
+ /* Make sure the array is big enough .. */
+ if (active_nr == active_alloc) {
+ active_alloc = alloc_nr(active_alloc);
+ active_cache = xrealloc(active_cache, active_alloc * sizeof(struct cache_entry *));
+ }
+
+ /* Add it in.. */
+ active_nr++;
+ if (active_nr > pos)
+ memmove(active_cache + pos + 1, active_cache + pos, (active_nr - pos - 1) * sizeof(ce));
+ active_cache[pos] = ce;
+ active_cache_changed = 1;
+ return 0;
+}
+
+static int verify_hdr(struct cache_header *hdr, unsigned long size)
+{
+ SHA_CTX c;
+ unsigned char sha1[20];
+
+ if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
+ return error("bad signature");
+ if (hdr->hdr_version != htonl(2))
+ return error("bad index version");
+ SHA1_Init(&c);
+ SHA1_Update(&c, hdr, size - 20);
+ SHA1_Final(sha1, &c);
+ if (memcmp(sha1, (void *)hdr + size - 20, 20))
+ return error("bad index file sha1 signature");
+ return 0;
+}
+
+int read_cache(void)
+{
+ int fd, i;
+ struct stat st;
+ unsigned long size, offset;
+ void *map;
+ struct cache_header *hdr;
+
+ errno = EBUSY;
+ if (active_cache)
+ return active_nr;
+
+ errno = ENOENT;
+ fd = open(get_index_file(), O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ return 0;
+ die("index file open failed (%s)", strerror(errno));
+ }
+
+ size = 0; // avoid gcc warning
+ map = MAP_FAILED;
+ if (!fstat(fd, &st)) {
+ size = st.st_size;
+ errno = EINVAL;
+ if (size >= sizeof(struct cache_header) + 20)
+ map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ }
+ close(fd);
+ if (map == MAP_FAILED)
+ die("index file mmap failed (%s)", strerror(errno));
+
+ hdr = map;
+ if (verify_hdr(hdr, size) < 0)
+ goto unmap;
+
+ active_nr = ntohl(hdr->hdr_entries);
+ active_alloc = alloc_nr(active_nr);
+ active_cache = calloc(active_alloc, sizeof(struct cache_entry *));
+
+ offset = sizeof(*hdr);
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = map + offset;
+ offset = offset + ce_size(ce);
+ active_cache[i] = ce;
+ }
+ return active_nr;
+
+unmap:
+ munmap(map, size);
+ errno = EINVAL;
+ die("index file corrupt");
+}
+
+#define WRITE_BUFFER_SIZE 8192
+static unsigned char write_buffer[WRITE_BUFFER_SIZE];
+static unsigned long write_buffer_len;
+
+static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len)
+{
+ while (len) {
+ unsigned int buffered = write_buffer_len;
+ unsigned int partial = WRITE_BUFFER_SIZE - buffered;
+ if (partial > len)
+ partial = len;
+ memcpy(write_buffer + buffered, data, partial);
+ buffered += partial;
+ if (buffered == WRITE_BUFFER_SIZE) {
+ SHA1_Update(context, write_buffer, WRITE_BUFFER_SIZE);
+ if (write(fd, write_buffer, WRITE_BUFFER_SIZE) != WRITE_BUFFER_SIZE)
+ return -1;
+ buffered = 0;
+ }
+ write_buffer_len = buffered;
+ len -= partial;
+ data += partial;
+ }
+ return 0;
+}
+
+static int ce_flush(SHA_CTX *context, int fd)
+{
+ unsigned int left = write_buffer_len;
+
+ if (left) {
+ write_buffer_len = 0;
+ SHA1_Update(context, write_buffer, left);
+ }
+
+ /* Flush first if not enough space for SHA1 signature */
+ if (left + 20 > WRITE_BUFFER_SIZE) {
+ if (write(fd, write_buffer, left) != left)
+ return -1;
+ left = 0;
+ }
+
+ /* Append the SHA1 signature at the end */
+ SHA1_Final(write_buffer + left, context);
+ left += 20;
+ if (write(fd, write_buffer, left) != left)
+ return -1;
+ return 0;
+}
+
+int write_cache(int newfd, struct cache_entry **cache, int entries)
+{
+ SHA_CTX c;
+ struct cache_header hdr;
+ int i, removed;
+
+ for (i = removed = 0; i < entries; i++)
+ if (!cache[i]->ce_mode)
+ removed++;
+
+ hdr.hdr_signature = htonl(CACHE_SIGNATURE);
+ hdr.hdr_version = htonl(2);
+ hdr.hdr_entries = htonl(entries - removed);
+
+ SHA1_Init(&c);
+ if (ce_write(&c, newfd, &hdr, sizeof(hdr)) < 0)
+ return -1;
+
+ for (i = 0; i < entries; i++) {
+ struct cache_entry *ce = cache[i];
+ if (!ce->ce_mode)
+ continue;
+ if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
+ return -1;
+ }
+ return ce_flush(&c, newfd);
+}
diff --git a/read-tree.c b/read-tree.c
new file mode 100644
index 000000000..df156ea0d
--- /dev/null
+++ b/read-tree.c
@@ -0,0 +1,728 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#define DBRT_DEBUG 1
+
+#include "cache.h"
+
+#include "object.h"
+#include "tree.h"
+
+static int merge = 0;
+static int update = 0;
+static int index_only = 0;
+static int nontrivial_merge = 0;
+static int trivial_merges_only = 0;
+
+static int head_idx = -1;
+static int merge_size = 0;
+
+static struct object_list *trees = NULL;
+
+static struct cache_entry df_conflict_entry = {
+};
+
+static struct tree_entry_list df_conflict_list = {
+ .name = NULL,
+ .next = &df_conflict_list
+};
+
+typedef int (*merge_fn_t)(struct cache_entry **src);
+
+static int entcmp(char *name1, int dir1, char *name2, int dir2)
+{
+ int len1 = strlen(name1);
+ int len2 = strlen(name2);
+ int len = len1 < len2 ? len1 : len2;
+ int ret = memcmp(name1, name2, len);
+ unsigned char c1, c2;
+ if (ret)
+ return ret;
+ c1 = name1[len];
+ c2 = name2[len];
+ if (!c1 && dir1)
+ c1 = '/';
+ if (!c2 && dir2)
+ c2 = '/';
+ ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0;
+ if (c1 && c2 && !ret)
+ ret = len1 - len2;
+ return ret;
+}
+
+static int unpack_trees_rec(struct tree_entry_list **posns, int len,
+ const char *base, merge_fn_t fn, int *indpos)
+{
+ int baselen = strlen(base);
+ int src_size = len + 1;
+ do {
+ int i;
+ char *first;
+ int firstdir = 0;
+ int pathlen;
+ unsigned ce_size;
+ struct tree_entry_list **subposns;
+ struct cache_entry **src;
+ int any_files = 0;
+ int any_dirs = 0;
+ char *cache_name;
+ int ce_stage;
+
+ /* Find the first name in the input. */
+
+ first = NULL;
+ cache_name = NULL;
+
+ /* Check the cache */
+ if (merge && *indpos < active_nr) {
+ /* This is a bit tricky: */
+ /* If the index has a subdirectory (with
+ * contents) as the first name, it'll get a
+ * filename like "foo/bar". But that's after
+ * "foo", so the entry in trees will get
+ * handled first, at which point we'll go into
+ * "foo", and deal with "bar" from the index,
+ * because the base will be "foo/". The only
+ * way we can actually have "foo/bar" first of
+ * all the things is if the trees don't
+ * contain "foo" at all, in which case we'll
+ * handle "foo/bar" without going into the
+ * directory, but that's fine (and will return
+ * an error anyway, with the added unknown
+ * file case.
+ */
+
+ cache_name = active_cache[*indpos]->name;
+ if (strlen(cache_name) > baselen &&
+ !memcmp(cache_name, base, baselen)) {
+ cache_name += baselen;
+ first = cache_name;
+ } else {
+ cache_name = NULL;
+ }
+ }
+
+#if DBRT_DEBUG > 1
+ if (first)
+ printf("index %s\n", first);
+#endif
+ for (i = 0; i < len; i++) {
+ if (!posns[i] || posns[i] == &df_conflict_list)
+ continue;
+#if DBRT_DEBUG > 1
+ printf("%d %s\n", i + 1, posns[i]->name);
+#endif
+ if (!first || entcmp(first, firstdir,
+ posns[i]->name,
+ posns[i]->directory) > 0) {
+ first = posns[i]->name;
+ firstdir = posns[i]->directory;
+ }
+ }
+ /* No name means we're done */
+ if (!first)
+ return 0;
+
+ pathlen = strlen(first);
+ ce_size = cache_entry_size(baselen + pathlen);
+
+ src = xmalloc(sizeof(struct cache_entry *) * src_size);
+ memset(src, 0, sizeof(struct cache_entry *) * src_size);
+
+ subposns = xmalloc(sizeof(struct tree_list_entry *) * len);
+ memset(subposns, 0, sizeof(struct tree_list_entry *) * len);
+
+ if (cache_name && !strcmp(cache_name, first)) {
+ any_files = 1;
+ src[0] = active_cache[*indpos];
+ remove_cache_entry_at(*indpos);
+ }
+
+ for (i = 0; i < len; i++) {
+ struct cache_entry *ce;
+
+ if (!posns[i] ||
+ (posns[i] != &df_conflict_list &&
+ strcmp(first, posns[i]->name))) {
+ continue;
+ }
+
+ if (posns[i] == &df_conflict_list) {
+ src[i + merge] = &df_conflict_entry;
+ continue;
+ }
+
+ if (posns[i]->directory) {
+ any_dirs = 1;
+ parse_tree(posns[i]->item.tree);
+ subposns[i] = posns[i]->item.tree->entries;
+ posns[i] = posns[i]->next;
+ src[i + merge] = &df_conflict_entry;
+ continue;
+ }
+
+ if (!merge)
+ ce_stage = 0;
+ else if (i + 1 < head_idx)
+ ce_stage = 1;
+ else if (i + 1 > head_idx)
+ ce_stage = 3;
+ else
+ ce_stage = 2;
+
+ ce = xmalloc(ce_size);
+ memset(ce, 0, ce_size);
+ ce->ce_mode = create_ce_mode(posns[i]->mode);
+ ce->ce_flags = create_ce_flags(baselen + pathlen,
+ ce_stage);
+ memcpy(ce->name, base, baselen);
+ memcpy(ce->name + baselen, first, pathlen + 1);
+
+ any_files = 1;
+
+ memcpy(ce->sha1, posns[i]->item.any->sha1, 20);
+ src[i + merge] = ce;
+ subposns[i] = &df_conflict_list;
+ posns[i] = posns[i]->next;
+ }
+ if (any_files) {
+ if (merge) {
+ int ret;
+
+#if DBRT_DEBUG > 1
+ printf("%s:\n", first);
+ for (i = 0; i < src_size; i++) {
+ printf(" %d ", i);
+ if (src[i])
+ printf("%s\n", sha1_to_hex(src[i]->sha1));
+ else
+ printf("\n");
+ }
+#endif
+ ret = fn(src);
+
+#if DBRT_DEBUG > 1
+ printf("Added %d entries\n", ret);
+#endif
+ *indpos += ret;
+ } else {
+ for (i = 0; i < src_size; i++) {
+ if (src[i]) {
+ add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+ }
+ }
+ }
+ }
+ if (any_dirs) {
+ char *newbase = xmalloc(baselen + 2 + pathlen);
+ memcpy(newbase, base, baselen);
+ memcpy(newbase + baselen, first, pathlen);
+ newbase[baselen + pathlen] = '/';
+ newbase[baselen + pathlen + 1] = '\0';
+ if (unpack_trees_rec(subposns, len, newbase, fn,
+ indpos))
+ return -1;
+ free(newbase);
+ }
+ free(subposns);
+ free(src);
+ } while (1);
+}
+
+static void reject_merge(struct cache_entry *ce)
+{
+ die("Entry '%s' would be overwritten by merge. Cannot merge.",
+ ce->name);
+}
+
+/* Unlink the last component and attempt to remove leading
+ * directories, in case this unlink is the removal of the
+ * last entry in the directory -- empty directories are removed.
+ */
+static void unlink_entry(char *name)
+{
+ char *cp, *prev;
+
+ if (unlink(name))
+ return;
+ prev = NULL;
+ while (1) {
+ int status;
+ cp = strrchr(name, '/');
+ if (prev)
+ *prev = '/';
+ if (!cp)
+ break;
+
+ *cp = 0;
+ status = rmdir(name);
+ if (status) {
+ *cp = '/';
+ break;
+ }
+ prev = cp;
+ }
+}
+
+static void check_updates(struct cache_entry **src, int nr)
+{
+ static struct checkout state = {
+ .base_dir = "",
+ .force = 1,
+ .quiet = 1,
+ .refresh_cache = 1,
+ };
+ unsigned short mask = htons(CE_UPDATE);
+ while (nr--) {
+ struct cache_entry *ce = *src++;
+ if (!ce->ce_mode) {
+ if (update)
+ unlink_entry(ce->name);
+ continue;
+ }
+ if (ce->ce_flags & mask) {
+ ce->ce_flags &= ~mask;
+ if (update)
+ checkout_entry(ce, &state);
+ }
+ }
+}
+
+static int unpack_trees(merge_fn_t fn)
+{
+ int indpos = 0;
+ unsigned len = object_list_length(trees);
+ struct tree_entry_list **posns =
+ xmalloc(len * sizeof(struct tree_entry_list *));
+ int i;
+ struct object_list *posn = trees;
+ merge_size = len;
+ for (i = 0; i < len; i++) {
+ posns[i] = ((struct tree *) posn->item)->entries;
+ posn = posn->next;
+ }
+ if (unpack_trees_rec(posns, len, "", fn, &indpos))
+ return -1;
+
+ if (trivial_merges_only && nontrivial_merge)
+ die("Merge requires file-level merging");
+
+ check_updates(active_cache, active_nr);
+ return 0;
+}
+
+static int list_tree(unsigned char *sha1)
+{
+ struct tree *tree = parse_tree_indirect(sha1);
+ if (!tree)
+ return -1;
+ object_list_append(&tree->object, &trees);
+ return 0;
+}
+
+static int same(struct cache_entry *a, struct cache_entry *b)
+{
+ if (!!a != !!b)
+ return 0;
+ if (!a && !b)
+ return 1;
+ return a->ce_mode == b->ce_mode &&
+ !memcmp(a->sha1, b->sha1, 20);
+}
+
+
+/*
+ * When a CE gets turned into an unmerged entry, we
+ * want it to be up-to-date
+ */
+static void verify_uptodate(struct cache_entry *ce)
+{
+ struct stat st;
+
+ if (index_only)
+ return;
+
+ if (!lstat(ce->name, &st)) {
+ unsigned changed = ce_match_stat(ce, &st);
+ if (!changed)
+ return;
+ errno = 0;
+ }
+ if (errno == ENOENT)
+ return;
+ die("Entry '%s' not uptodate. Cannot merge.", ce->name);
+}
+
+static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
+{
+ merge->ce_flags |= htons(CE_UPDATE);
+ if (old) {
+ /*
+ * See if we can re-use the old CE directly?
+ * That way we get the uptodate stat info.
+ *
+ * This also removes the UPDATE flag on
+ * a match.
+ */
+ if (same(old, merge)) {
+ *merge = *old;
+ } else {
+ verify_uptodate(old);
+ }
+ }
+ merge->ce_flags &= ~htons(CE_STAGEMASK);
+ add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
+ return 1;
+}
+
+static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
+{
+ if (old)
+ verify_uptodate(old);
+ ce->ce_mode = 0;
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+ return 1;
+}
+
+static int keep_entry(struct cache_entry *ce)
+{
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+ return 1;
+}
+
+#if DBRT_DEBUG
+static void show_stage_entry(FILE *o,
+ const char *label, const struct cache_entry *ce)
+{
+ if (!ce)
+ fprintf(o, "%s (missing)\n", label);
+ else
+ fprintf(o, "%s%06o %s %d\t%s\n",
+ label,
+ ntohl(ce->ce_mode),
+ sha1_to_hex(ce->sha1),
+ ce_stage(ce),
+ ce->name);
+}
+#endif
+
+static int threeway_merge(struct cache_entry **stages)
+{
+ struct cache_entry *index;
+ struct cache_entry *head;
+ struct cache_entry *remote = stages[head_idx + 1];
+ int count;
+ int head_match = 0;
+ int remote_match = 0;
+
+ int df_conflict_head = 0;
+ int df_conflict_remote = 0;
+
+ int any_anc_missing = 0;
+ int i;
+
+ for (i = 1; i < head_idx; i++) {
+ if (!stages[i])
+ any_anc_missing = 1;
+ }
+
+ index = stages[0];
+ head = stages[head_idx];
+
+ if (head == &df_conflict_entry) {
+ df_conflict_head = 1;
+ head = NULL;
+ }
+
+ if (remote == &df_conflict_entry) {
+ df_conflict_remote = 1;
+ remote = NULL;
+ }
+
+ /* First, if there's a #16 situation, note that to prevent #13
+ * and #14.
+ */
+ if (!same(remote, head)) {
+ for (i = 1; i < head_idx; i++) {
+ if (same(stages[i], head)) {
+ head_match = i;
+ }
+ if (same(stages[i], remote)) {
+ remote_match = i;
+ }
+ }
+ }
+
+ /* We start with cases where the index is allowed to match
+ * something other than the head: #14(ALT) and #2ALT, where it
+ * is permitted to match the result instead.
+ */
+ /* #14, #14ALT, #2ALT */
+ if (remote && !df_conflict_head && head_match && !remote_match) {
+ if (index && !same(index, remote) && !same(index, head))
+ reject_merge(index);
+ return merged_entry(remote, index);
+ }
+ /*
+ * If we have an entry in the index cache, then we want to
+ * make sure that it matches head.
+ */
+ if (index && !same(index, head)) {
+ reject_merge(index);
+ }
+
+ if (head) {
+ /* #5ALT, #15 */
+ if (same(head, remote))
+ return merged_entry(head, index);
+ /* #13, #3ALT */
+ if (!df_conflict_remote && remote_match && !head_match)
+ return merged_entry(head, index);
+ }
+
+ /* #1 */
+ if (!head && !remote && any_anc_missing)
+ return 0;
+
+ /* Below are "no merge" cases, which require that the index be
+ * up-to-date to avoid the files getting overwritten with
+ * conflict resolution files.
+ */
+ if (index) {
+ verify_uptodate(index);
+ }
+
+ nontrivial_merge = 1;
+
+ /* #2, #3, #4, #6, #7, #9, #11. */
+ count = 0;
+ if (!head_match || !remote_match) {
+ for (i = 1; i < head_idx; i++) {
+ if (stages[i]) {
+ keep_entry(stages[i]);
+ count++;
+ break;
+ }
+ }
+ }
+#if DBRT_DEBUG
+ else {
+ fprintf(stderr, "read-tree: warning #16 detected\n");
+ show_stage_entry(stderr, "head ", stages[head_match]);
+ show_stage_entry(stderr, "remote ", stages[remote_match]);
+ }
+#endif
+ if (head) { count += keep_entry(head); }
+ if (remote) { count += keep_entry(remote); }
+ return count;
+}
+
+/*
+ * Two-way merge.
+ *
+ * The rule is to "carry forward" what is in the index without losing
+ * information across a "fast forward", favoring a successful merge
+ * over a merge failure when it makes sense. For details of the
+ * "carry forward" rule, please see <Documentation/git-read-tree.txt>.
+ *
+ */
+static int twoway_merge(struct cache_entry **src)
+{
+ struct cache_entry *current = src[0];
+ struct cache_entry *oldtree = src[1], *newtree = src[2];
+
+ if (merge_size != 2)
+ return error("Cannot do a twoway merge of %d trees\n",
+ merge_size);
+
+ if (current) {
+ if ((!oldtree && !newtree) || /* 4 and 5 */
+ (!oldtree && newtree &&
+ same(current, newtree)) || /* 6 and 7 */
+ (oldtree && newtree &&
+ same(oldtree, newtree)) || /* 14 and 15 */
+ (oldtree && newtree &&
+ !same(oldtree, newtree) && /* 18 and 19*/
+ same(current, newtree))) {
+ return keep_entry(current);
+ }
+ else if (oldtree && !newtree && same(current, oldtree)) {
+ /* 10 or 11 */
+ return deleted_entry(oldtree, current);
+ }
+ else if (oldtree && newtree &&
+ same(current, oldtree) && !same(current, newtree)) {
+ /* 20 or 21 */
+ return merged_entry(newtree, current);
+ }
+ else {
+ /* all other failures */
+ if (oldtree)
+ reject_merge(oldtree);
+ if (current)
+ reject_merge(current);
+ if (newtree)
+ reject_merge(newtree);
+ return -1;
+ }
+ }
+ else if (newtree)
+ return merged_entry(newtree, current);
+ else
+ return deleted_entry(oldtree, current);
+}
+
+/*
+ * One-way merge.
+ *
+ * The rule is:
+ * - take the stat information from stage0, take the data from stage1
+ */
+static int oneway_merge(struct cache_entry **src)
+{
+ struct cache_entry *old = src[0];
+ struct cache_entry *a = src[1];
+
+ if (merge_size != 1)
+ return error("Cannot do a oneway merge of %d trees\n",
+ merge_size);
+
+ if (!a)
+ return 0;
+ if (old && same(old, a)) {
+ return keep_entry(old);
+ }
+ return merged_entry(a, NULL);
+}
+
+static int read_cache_unmerged(void)
+{
+ int i, deleted;
+ struct cache_entry **dst;
+
+ read_cache();
+ dst = active_cache;
+ deleted = 0;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ce_stage(ce)) {
+ deleted++;
+ continue;
+ }
+ if (deleted)
+ *dst = ce;
+ dst++;
+ }
+ active_nr -= deleted;
+ return deleted;
+}
+
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u | -i] <sha1> [<sha2> [<sha3>]])";
+
+static struct cache_file cache_file;
+
+int main(int argc, char **argv)
+{
+ int i, newfd, reset, stage = 0;
+ unsigned char sha1[20];
+ merge_fn_t fn = NULL;
+
+ newfd = hold_index_file_for_update(&cache_file, get_index_file());
+ if (newfd < 0)
+ die("unable to create new cachefile");
+
+ git_config(git_default_config);
+
+ merge = 0;
+ reset = 0;
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ /* "-u" means "update", meaning that a merge will update
+ * the working tree.
+ */
+ if (!strcmp(arg, "-u")) {
+ update = 1;
+ continue;
+ }
+
+ /* "-i" means "index only", meaning that a merge will
+ * not even look at the working tree.
+ */
+ if (!strcmp(arg, "-i")) {
+ index_only = 1;
+ continue;
+ }
+
+ /* This differs from "-m" in that we'll silently ignore unmerged entries */
+ if (!strcmp(arg, "--reset")) {
+ if (stage || merge)
+ usage(read_tree_usage);
+ reset = 1;
+ merge = 1;
+ stage = 1;
+ read_cache_unmerged();
+ continue;
+ }
+
+ if (!strcmp(arg, "--trivial")) {
+ trivial_merges_only = 1;
+ continue;
+ }
+
+ /* "-m" stands for "merge", meaning we start in stage 1 */
+ if (!strcmp(arg, "-m")) {
+ if (stage || merge)
+ usage(read_tree_usage);
+ if (read_cache_unmerged())
+ die("you need to resolve your current index first");
+ stage = 1;
+ merge = 1;
+ continue;
+ }
+
+ /* using -u and -i at the same time makes no sense */
+ if (1 < index_only + update)
+ usage(read_tree_usage);
+
+ if (get_sha1(arg, sha1) < 0)
+ usage(read_tree_usage);
+ if (list_tree(sha1) < 0)
+ die("failed to unpack tree object %s", arg);
+ stage++;
+ }
+ if ((update||index_only) && !merge)
+ usage(read_tree_usage);
+
+ if (merge) {
+ if (stage < 2)
+ die("just how do you expect me to merge %d trees?", stage-1);
+ switch (stage - 1) {
+ case 1:
+ fn = oneway_merge;
+ break;
+ case 2:
+ fn = twoway_merge;
+ break;
+ case 3:
+ fn = threeway_merge;
+ break;
+ default:
+ fn = threeway_merge;
+ break;
+ }
+
+ if (stage - 1 >= 3)
+ head_idx = stage - 2;
+ else
+ head_idx = 1;
+ }
+
+ unpack_trees(fn);
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_index_file(&cache_file))
+ die("unable to write new index file");
+ return 0;
+}
diff --git a/receive-pack.c b/receive-pack.c
new file mode 100644
index 000000000..8f157bc3f
--- /dev/null
+++ b/receive-pack.c
@@ -0,0 +1,291 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "run-command.h"
+#include <sys/wait.h>
+
+static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+
+static const char unpacker[] = "git-unpack-objects";
+
+static int show_ref(const char *path, const unsigned char *sha1)
+{
+ packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
+ return 0;
+}
+
+static void write_head_info(void)
+{
+ for_each_ref(show_ref);
+}
+
+struct command {
+ struct command *next;
+ unsigned char updated;
+ unsigned char old_sha1[20];
+ unsigned char new_sha1[20];
+ char ref_name[0];
+};
+
+static struct command *commands = NULL;
+
+static int is_all_zeroes(const char *hex)
+{
+ int i;
+ for (i = 0; i < 40; i++)
+ if (*hex++ != '0')
+ return 0;
+ return 1;
+}
+
+static int verify_old_ref(const char *name, char *hex_contents)
+{
+ int fd, ret;
+ char buffer[60];
+
+ if (is_all_zeroes(hex_contents))
+ return 0;
+ fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ ret = read(fd, buffer, 40);
+ close(fd);
+ if (ret != 40)
+ return -1;
+ if (memcmp(buffer, hex_contents, 40))
+ return -1;
+ return 0;
+}
+
+static char update_hook[] = "hooks/update";
+
+static int run_update_hook(const char *refname,
+ char *old_hex, char *new_hex)
+{
+ int code;
+
+ if (access(update_hook, X_OK) < 0)
+ return 0;
+ code = run_command(update_hook, refname, old_hex, new_hex, NULL);
+ switch (code) {
+ case 0:
+ return 0;
+ case -ERR_RUN_COMMAND_FORK:
+ die("hook fork failed");
+ case -ERR_RUN_COMMAND_EXEC:
+ die("hook execute failed");
+ case -ERR_RUN_COMMAND_WAITPID:
+ die("waitpid failed");
+ case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+ die("waitpid is confused");
+ case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+ fprintf(stderr, "%s died of signal", update_hook);
+ return -1;
+ case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+ die("%s died strangely", update_hook);
+ default:
+ error("%s exited with error code %d", update_hook, -code);
+ return -code;
+ }
+}
+
+static int update(const char *name,
+ unsigned char *old_sha1, unsigned char *new_sha1)
+{
+ char new_hex[60], *old_hex, *lock_name;
+ int newfd, namelen, written;
+
+ if (!strncmp(name, "refs/", 5) && check_ref_format(name + 5))
+ return error("refusing to create funny ref '%s' locally",
+ name);
+
+ namelen = strlen(name);
+ lock_name = xmalloc(namelen + 10);
+ memcpy(lock_name, name, namelen);
+ memcpy(lock_name + namelen, ".lock", 6);
+
+ strcpy(new_hex, sha1_to_hex(new_sha1));
+ old_hex = sha1_to_hex(old_sha1);
+ if (!has_sha1_file(new_sha1))
+ return error("unpack should have generated %s, "
+ "but I can't find it!", new_hex);
+
+ safe_create_leading_directories(lock_name);
+
+ newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ if (newfd < 0)
+ return error("unable to create %s (%s)",
+ lock_name, strerror(errno));
+
+ /* Write the ref with an ending '\n' */
+ new_hex[40] = '\n';
+ new_hex[41] = 0;
+ written = write(newfd, new_hex, 41);
+ /* Remove the '\n' again */
+ new_hex[40] = 0;
+
+ close(newfd);
+ if (written != 41) {
+ unlink(lock_name);
+ return error("unable to write %s", lock_name);
+ }
+ if (verify_old_ref(name, old_hex) < 0) {
+ unlink(lock_name);
+ return error("%s changed during push", name);
+ }
+ if (run_update_hook(name, old_hex, new_hex)) {
+ unlink(lock_name);
+ return error("hook declined to update %s\n", name);
+ }
+ else if (rename(lock_name, name) < 0) {
+ unlink(lock_name);
+ return error("unable to replace %s", name);
+ }
+ else {
+ fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
+ return 0;
+ }
+}
+
+static char update_post_hook[] = "hooks/post-update";
+
+static void run_update_post_hook(struct command *cmd)
+{
+ struct command *cmd_p;
+ int argc;
+ char **argv;
+
+ if (access(update_post_hook, X_OK) < 0)
+ return;
+ for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+ if (!cmd_p->updated)
+ continue;
+ argc++;
+ }
+ argv = xmalloc(sizeof(*argv) * (1 + argc));
+ argv[0] = update_post_hook;
+
+ for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+ if (!cmd_p->updated)
+ continue;
+ argv[argc] = xmalloc(strlen(cmd_p->ref_name) + 1);
+ strcpy(argv[argc], cmd_p->ref_name);
+ argc++;
+ }
+ argv[argc] = NULL;
+ run_command_v(argc, argv);
+}
+
+/*
+ * This gets called after(if) we've successfully
+ * unpacked the data payload.
+ */
+static void execute_commands(void)
+{
+ struct command *cmd = commands;
+
+ while (cmd) {
+ cmd->updated = !update(cmd->ref_name,
+ cmd->old_sha1, cmd->new_sha1);
+ cmd = cmd->next;
+ }
+ run_update_post_hook(commands);
+}
+
+static void read_head_info(void)
+{
+ struct command **p = &commands;
+ for (;;) {
+ static char line[1000];
+ unsigned char old_sha1[20], new_sha1[20];
+ struct command *cmd;
+ int len;
+
+ len = packet_read_line(0, line, sizeof(line));
+ if (!len)
+ break;
+ if (line[len-1] == '\n')
+ line[--len] = 0;
+ if (len < 83 ||
+ line[40] != ' ' ||
+ line[81] != ' ' ||
+ get_sha1_hex(line, old_sha1) ||
+ get_sha1_hex(line + 41, new_sha1))
+ die("protocol error: expected old/new/ref, got '%s'", line);
+ cmd = xmalloc(sizeof(struct command) + len - 80);
+ memcpy(cmd->old_sha1, old_sha1, 20);
+ memcpy(cmd->new_sha1, new_sha1, 20);
+ memcpy(cmd->ref_name, line + 82, len - 81);
+ cmd->next = NULL;
+ *p = cmd;
+ p = &cmd->next;
+ }
+}
+
+static void unpack(void)
+{
+ int code = run_command(unpacker, NULL);
+ switch (code) {
+ case 0:
+ return;
+ case -ERR_RUN_COMMAND_FORK:
+ die("unpack fork failed");
+ case -ERR_RUN_COMMAND_EXEC:
+ die("unpack execute failed");
+ case -ERR_RUN_COMMAND_WAITPID:
+ die("waitpid failed");
+ case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+ die("waitpid is confused");
+ case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+ die("%s died of signal", unpacker);
+ case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+ die("%s died strangely", unpacker);
+ default:
+ die("%s exited with error code %d", unpacker, -code);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ const char *dir = NULL;
+
+ argv++;
+ for (i = 1; i < argc; i++) {
+ const char *arg = *argv++;
+
+ if (*arg == '-') {
+ /* Do flag handling here */
+ usage(receive_pack_usage);
+ }
+ if (dir)
+ usage(receive_pack_usage);
+ dir = arg;
+ }
+ if (!dir)
+ usage(receive_pack_usage);
+
+ /* chdir to the directory. If that fails, try appending ".git" */
+ if (chdir(dir) < 0) {
+ if (chdir(mkpath("%s.git", dir)) < 0)
+ die("unable to cd to %s", dir);
+ }
+
+ /* If we have a ".git" directory, chdir to it */
+ chdir(".git");
+ putenv("GIT_DIR=.");
+
+ if (access("objects", X_OK) < 0 || access("refs/heads", X_OK) < 0)
+ die("%s doesn't appear to be a git directory", dir);
+ write_head_info();
+
+ /* EOF */
+ packet_flush(1);
+
+ read_head_info();
+ if (commands) {
+ unpack();
+ execute_commands();
+ }
+ return 0;
+}
diff --git a/refs.c b/refs.c
new file mode 100644
index 000000000..f324be503
--- /dev/null
+++ b/refs.c
@@ -0,0 +1,410 @@
+#include "refs.h"
+#include "cache.h"
+
+#include <errno.h>
+
+/* We allow "recursive" symbolic refs. Only within reason, though */
+#define MAXDEPTH 5
+
+#ifndef USE_SYMLINK_HEAD
+#define USE_SYMLINK_HEAD 1
+#endif
+
+int validate_symref(const char *path)
+{
+ struct stat st;
+ char *buf, buffer[256];
+ int len, fd;
+
+ if (lstat(path, &st) < 0)
+ return -1;
+
+ /* Make sure it is a "refs/.." symlink */
+ if (S_ISLNK(st.st_mode)) {
+ len = readlink(path, buffer, sizeof(buffer)-1);
+ if (len >= 5 && !memcmp("refs/", buffer, 5))
+ return 0;
+ return -1;
+ }
+
+ /*
+ * Anything else, just open it and try to see if it is a symbolic ref.
+ */
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ len = read(fd, buffer, sizeof(buffer)-1);
+ close(fd);
+
+ /*
+ * Is it a symbolic ref?
+ */
+ if (len < 4 || memcmp("ref:", buffer, 4))
+ return -1;
+ buf = buffer + 4;
+ len -= 4;
+ while (len && isspace(*buf))
+ buf++, len--;
+ if (len >= 5 && !memcmp("refs/", buf, 5))
+ return 0;
+ return -1;
+}
+
+const char *resolve_ref(const char *path, unsigned char *sha1, int reading)
+{
+ int depth = MAXDEPTH, len;
+ char buffer[256];
+
+ for (;;) {
+ struct stat st;
+ char *buf;
+ int fd;
+
+ if (--depth < 0)
+ return NULL;
+
+ /* Special case: non-existing file.
+ * Not having the refs/heads/new-branch is OK
+ * if we are writing into it, so is .git/HEAD
+ * that points at refs/heads/master still to be
+ * born. It is NOT OK if we are resolving for
+ * reading.
+ */
+ if (lstat(path, &st) < 0) {
+ if (reading || errno != ENOENT)
+ return NULL;
+ memset(sha1, 0, 20);
+ return path;
+ }
+
+ /* Follow "normalized" - ie "refs/.." symlinks by hand */
+ if (S_ISLNK(st.st_mode)) {
+ len = readlink(path, buffer, sizeof(buffer)-1);
+ if (len >= 5 && !memcmp("refs/", buffer, 5)) {
+ path = git_path("%.*s", len, buffer);
+ continue;
+ }
+ }
+
+ /*
+ * Anything else, just open it and try to use it as
+ * a ref
+ */
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+ len = read(fd, buffer, sizeof(buffer)-1);
+ close(fd);
+
+ /*
+ * Is it a symbolic ref?
+ */
+ if (len < 4 || memcmp("ref:", buffer, 4))
+ break;
+ buf = buffer + 4;
+ len -= 4;
+ while (len && isspace(*buf))
+ buf++, len--;
+ while (len && isspace(buf[len-1]))
+ buf[--len] = 0;
+ path = git_path("%.*s", len, buf);
+ }
+ if (len < 40 || get_sha1_hex(buffer, sha1))
+ return NULL;
+ return path;
+}
+
+int create_symref(const char *git_HEAD, const char *refs_heads_master)
+{
+ const char *lockpath;
+ char ref[1000];
+ int fd, len, written;
+
+#if USE_SYMLINK_HEAD
+ if (!only_use_symrefs) {
+ unlink(git_HEAD);
+ if (!symlink(refs_heads_master, git_HEAD))
+ return 0;
+ fprintf(stderr, "no symlink - falling back to symbolic ref\n");
+ }
+#endif
+
+ len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
+ if (sizeof(ref) <= len) {
+ error("refname too long: %s", refs_heads_master);
+ return -1;
+ }
+ lockpath = mkpath("%s.lock", git_HEAD);
+ fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ written = write(fd, ref, len);
+ close(fd);
+ if (written != len) {
+ unlink(lockpath);
+ error("Unable to write to %s", lockpath);
+ return -2;
+ }
+ if (rename(lockpath, git_HEAD) < 0) {
+ unlink(lockpath);
+ error("Unable to create %s", git_HEAD);
+ return -3;
+ }
+ return 0;
+}
+
+int read_ref(const char *filename, unsigned char *sha1)
+{
+ if (resolve_ref(filename, sha1, 1))
+ return 0;
+ return -1;
+}
+
+static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1))
+{
+ int retval = 0;
+ DIR *dir = opendir(git_path("%s", base));
+
+ if (dir) {
+ struct dirent *de;
+ int baselen = strlen(base);
+ char *path = xmalloc(baselen + 257);
+
+ if (!strncmp(base, "./", 2)) {
+ base += 2;
+ baselen -= 2;
+ }
+ memcpy(path, base, baselen);
+ if (baselen && base[baselen-1] != '/')
+ path[baselen++] = '/';
+
+ while ((de = readdir(dir)) != NULL) {
+ unsigned char sha1[20];
+ struct stat st;
+ int namelen;
+
+ if (de->d_name[0] == '.')
+ continue;
+ namelen = strlen(de->d_name);
+ if (namelen > 255)
+ continue;
+ memcpy(path + baselen, de->d_name, namelen+1);
+ if (stat(git_path("%s", path), &st) < 0)
+ continue;
+ if (S_ISDIR(st.st_mode)) {
+ retval = do_for_each_ref(path, fn);
+ if (retval)
+ break;
+ continue;
+ }
+ if (read_ref(git_path("%s", path), sha1) < 0)
+ continue;
+ if (!has_sha1_file(sha1))
+ continue;
+ retval = fn(path, sha1);
+ if (retval)
+ break;
+ }
+ free(path);
+ closedir(dir);
+ }
+ return retval;
+}
+
+int head_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+ unsigned char sha1[20];
+ if (!read_ref(git_path("HEAD"), sha1))
+ return fn("HEAD", sha1);
+ return 0;
+}
+
+int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+ return do_for_each_ref("refs", fn);
+}
+
+static char *ref_file_name(const char *ref)
+{
+ char *base = get_refs_directory();
+ int baselen = strlen(base);
+ int reflen = strlen(ref);
+ char *ret = xmalloc(baselen + 2 + reflen);
+ sprintf(ret, "%s/%s", base, ref);
+ return ret;
+}
+
+static char *ref_lock_file_name(const char *ref)
+{
+ char *base = get_refs_directory();
+ int baselen = strlen(base);
+ int reflen = strlen(ref);
+ char *ret = xmalloc(baselen + 7 + reflen);
+ sprintf(ret, "%s/%s.lock", base, ref);
+ return ret;
+}
+
+int get_ref_sha1(const char *ref, unsigned char *sha1)
+{
+ const char *filename;
+
+ if (check_ref_format(ref))
+ return -1;
+ filename = git_path("refs/%s", ref);
+ return read_ref(filename, sha1);
+}
+
+static int lock_ref_file(const char *filename, const char *lock_filename,
+ const unsigned char *old_sha1)
+{
+ int fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ unsigned char current_sha1[20];
+ int retval;
+ if (fd < 0) {
+ return error("Couldn't open lock file for %s: %s",
+ filename, strerror(errno));
+ }
+ retval = read_ref(filename, current_sha1);
+ if (old_sha1) {
+ if (retval) {
+ close(fd);
+ unlink(lock_filename);
+ return error("Could not read the current value of %s",
+ filename);
+ }
+ if (memcmp(current_sha1, old_sha1, 20)) {
+ close(fd);
+ unlink(lock_filename);
+ error("The current value of %s is %s",
+ filename, sha1_to_hex(current_sha1));
+ return error("Expected %s",
+ sha1_to_hex(old_sha1));
+ }
+ } else {
+ if (!retval) {
+ close(fd);
+ unlink(lock_filename);
+ return error("Unexpectedly found a value of %s for %s",
+ sha1_to_hex(current_sha1), filename);
+ }
+ }
+ return fd;
+}
+
+int lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
+{
+ char *filename;
+ char *lock_filename;
+ int retval;
+ if (check_ref_format(ref))
+ return -1;
+ filename = ref_file_name(ref);
+ lock_filename = ref_lock_file_name(ref);
+ retval = lock_ref_file(filename, lock_filename, old_sha1);
+ free(filename);
+ free(lock_filename);
+ return retval;
+}
+
+static int write_ref_file(const char *filename,
+ const char *lock_filename, int fd,
+ const unsigned char *sha1)
+{
+ char *hex = sha1_to_hex(sha1);
+ char term = '\n';
+ if (write(fd, hex, 40) < 40 ||
+ write(fd, &term, 1) < 1) {
+ error("Couldn't write %s\n", filename);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ rename(lock_filename, filename);
+ return 0;
+}
+
+int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
+{
+ char *filename;
+ char *lock_filename;
+ int retval;
+ if (fd < 0)
+ return -1;
+ if (check_ref_format(ref))
+ return -1;
+ filename = ref_file_name(ref);
+ lock_filename = ref_lock_file_name(ref);
+ retval = write_ref_file(filename, lock_filename, fd, sha1);
+ free(filename);
+ free(lock_filename);
+ return retval;
+}
+
+/*
+ * Make sure "ref" is something reasonable to have under ".git/refs/";
+ * We do not like it if:
+ *
+ * - any path component of it begins with ".", or
+ * - it has double dots "..", or
+ * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
+ * - it ends with a "/".
+ */
+
+static inline int bad_ref_char(int ch)
+{
+ return (((unsigned) ch) <= ' ' ||
+ ch == '~' || ch == '^' || ch == ':');
+}
+
+int check_ref_format(const char *ref)
+{
+ int ch, level;
+ const char *cp = ref;
+
+ level = 0;
+ while (1) {
+ while ((ch = *cp++) == '/')
+ ; /* tolerate duplicated slashes */
+ if (!ch)
+ return -1; /* should not end with slashes */
+
+ /* we are at the beginning of the path component */
+ if (ch == '.' || bad_ref_char(ch))
+ return -1;
+
+ /* scan the rest of the path component */
+ while ((ch = *cp++) != 0) {
+ if (bad_ref_char(ch))
+ return -1;
+ if (ch == '/')
+ break;
+ if (ch == '.' && *cp == '.')
+ return -1;
+ }
+ level++;
+ if (!ch) {
+ if (level < 2)
+ return -1; /* at least of form "heads/blah" */
+ return 0;
+ }
+ }
+}
+
+int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
+{
+ char *filename;
+ char *lock_filename;
+ int fd;
+ int retval;
+ if (check_ref_format(ref))
+ return -1;
+ filename = ref_file_name(ref);
+ lock_filename = ref_lock_file_name(ref);
+ fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (fd < 0) {
+ error("Writing %s", lock_filename);
+ perror("Open");
+ }
+ retval = write_ref_file(filename, lock_filename, fd, sha1);
+ free(filename);
+ free(lock_filename);
+ return retval;
+}
diff --git a/refs.h b/refs.h
new file mode 100644
index 000000000..262559670
--- /dev/null
+++ b/refs.h
@@ -0,0 +1,28 @@
+#ifndef REFS_H
+#define REFS_H
+
+/*
+ * Calls the specified function for each ref file until it returns nonzero,
+ * and returns the value
+ */
+extern int head_ref(int (*fn)(const char *path, const unsigned char *sha1));
+extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
+
+/** Reads the refs file specified into sha1 **/
+extern int get_ref_sha1(const char *ref, unsigned char *sha1);
+
+/** Locks ref and returns the fd to give to write_ref_sha1() if the ref
+ * has the given value currently; otherwise, returns -1.
+ **/
+extern int lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
+
+/** Writes sha1 into the refs file specified, locked with the given fd. **/
+extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1);
+
+/** Writes sha1 into the refs file specified. **/
+extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
+
+/** Returns 0 if target has the right format for a ref. **/
+extern int check_ref_format(const char *target);
+
+#endif /* REFS_H */
diff --git a/rev-list.c b/rev-list.c
new file mode 100644
index 000000000..6e6ffde39
--- /dev/null
+++ b/rev-list.c
@@ -0,0 +1,881 @@
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "epoch.h"
+#include "diff.h"
+
+#define SEEN (1u << 0)
+#define INTERESTING (1u << 1)
+#define COUNTED (1u << 2)
+#define SHOWN (1u << 3)
+#define TREECHANGE (1u << 4)
+
+static const char rev_list_usage[] =
+"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
+" limiting output:\n"
+" --max-count=nr\n"
+" --max-age=epoch\n"
+" --min-age=epoch\n"
+" --sparse\n"
+" --no-merges\n"
+" --all\n"
+" ordering output:\n"
+" --merge-order [ --show-breaks ]\n"
+" --topo-order\n"
+" formatting output:\n"
+" --parents\n"
+" --objects\n"
+" --unpacked\n"
+" --header | --pretty\n"
+" special purpose:\n"
+" --bisect"
+;
+
+static int dense = 1;
+static int unpacked = 0;
+static int bisect_list = 0;
+static int tag_objects = 0;
+static int tree_objects = 0;
+static int blob_objects = 0;
+static int verbose_header = 0;
+static int show_parents = 0;
+static int hdr_termination = 0;
+static const char *commit_prefix = "";
+static unsigned long max_age = -1;
+static unsigned long min_age = -1;
+static int max_count = -1;
+static enum cmit_fmt commit_format = CMIT_FMT_RAW;
+static int merge_order = 0;
+static int show_breaks = 0;
+static int stop_traversal = 0;
+static int topo_order = 0;
+static int no_merges = 0;
+static const char **paths = NULL;
+
+static void show_commit(struct commit *commit)
+{
+ commit->object.flags |= SHOWN;
+ if (show_breaks) {
+ commit_prefix = "| ";
+ if (commit->object.flags & DISCONTINUITY) {
+ commit_prefix = "^ ";
+ } else if (commit->object.flags & BOUNDARY) {
+ commit_prefix = "= ";
+ }
+ }
+ printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1));
+ if (show_parents) {
+ struct commit_list *parents = commit->parents;
+ while (parents) {
+ printf(" %s", sha1_to_hex(parents->item->object.sha1));
+ parents = parents->next;
+ }
+ }
+ if (commit_format == CMIT_FMT_ONELINE)
+ putchar(' ');
+ else
+ putchar('\n');
+
+ if (verbose_header) {
+ static char pretty_header[16384];
+ pretty_print_commit(commit_format, commit->buffer, ~0, pretty_header, sizeof(pretty_header));
+ printf("%s%c", pretty_header, hdr_termination);
+ }
+ fflush(stdout);
+}
+
+static int rewrite_one(struct commit **pp)
+{
+ for (;;) {
+ struct commit *p = *pp;
+ if (p->object.flags & (TREECHANGE | UNINTERESTING))
+ return 0;
+ if (!p->parents)
+ return -1;
+ *pp = p->parents->item;
+ }
+}
+
+static void rewrite_parents(struct commit *commit)
+{
+ struct commit_list **pp = &commit->parents;
+ while (*pp) {
+ struct commit_list *parent = *pp;
+ if (rewrite_one(&parent->item) < 0) {
+ *pp = parent->next;
+ continue;
+ }
+ pp = &parent->next;
+ }
+}
+
+static int filter_commit(struct commit * commit)
+{
+ if (stop_traversal && (commit->object.flags & BOUNDARY))
+ return STOP;
+ if (commit->object.flags & (UNINTERESTING|SHOWN))
+ return CONTINUE;
+ if (min_age != -1 && (commit->date > min_age))
+ return CONTINUE;
+ if (max_age != -1 && (commit->date < max_age)) {
+ stop_traversal=1;
+ return CONTINUE;
+ }
+ if (max_count != -1 && !max_count--)
+ return STOP;
+ if (no_merges && (commit->parents && commit->parents->next))
+ return CONTINUE;
+ if (paths && dense) {
+ if (!(commit->object.flags & TREECHANGE))
+ return CONTINUE;
+ rewrite_parents(commit);
+ }
+ return DO;
+}
+
+static int process_commit(struct commit * commit)
+{
+ int action=filter_commit(commit);
+
+ if (action == STOP) {
+ return STOP;
+ }
+
+ if (action == CONTINUE) {
+ return CONTINUE;
+ }
+
+ show_commit(commit);
+
+ return CONTINUE;
+}
+
+static struct object_list **add_object(struct object *obj, struct object_list **p, const char *name)
+{
+ struct object_list *entry = xmalloc(sizeof(*entry));
+ entry->item = obj;
+ entry->next = *p;
+ entry->name = name;
+ *p = entry;
+ return &entry->next;
+}
+
+static struct object_list **process_blob(struct blob *blob, struct object_list **p, const char *name)
+{
+ struct object *obj = &blob->object;
+
+ if (!blob_objects)
+ return p;
+ if (obj->flags & (UNINTERESTING | SEEN))
+ return p;
+ obj->flags |= SEEN;
+ return add_object(obj, p, name);
+}
+
+static struct object_list **process_tree(struct tree *tree, struct object_list **p, const char *name)
+{
+ struct object *obj = &tree->object;
+ struct tree_entry_list *entry;
+
+ if (!tree_objects)
+ return p;
+ if (obj->flags & (UNINTERESTING | SEEN))
+ return p;
+ if (parse_tree(tree) < 0)
+ die("bad tree object %s", sha1_to_hex(obj->sha1));
+ obj->flags |= SEEN;
+ p = add_object(obj, p, name);
+ entry = tree->entries;
+ tree->entries = NULL;
+ while (entry) {
+ struct tree_entry_list *next = entry->next;
+ if (entry->directory)
+ p = process_tree(entry->item.tree, p, entry->name);
+ else
+ p = process_blob(entry->item.blob, p, entry->name);
+ free(entry);
+ entry = next;
+ }
+ return p;
+}
+
+static struct object_list *pending_objects = NULL;
+
+static void show_commit_list(struct commit_list *list)
+{
+ struct object_list *objects = NULL, **p = &objects, *pending;
+ while (list) {
+ struct commit *commit = pop_most_recent_commit(&list, SEEN);
+
+ p = process_tree(commit->tree, p, "");
+ if (process_commit(commit) == STOP)
+ break;
+ }
+ for (pending = pending_objects; pending; pending = pending->next) {
+ struct object *obj = pending->item;
+ const char *name = pending->name;
+ if (obj->flags & (UNINTERESTING | SEEN))
+ continue;
+ if (obj->type == tag_type) {
+ obj->flags |= SEEN;
+ p = add_object(obj, p, name);
+ continue;
+ }
+ if (obj->type == tree_type) {
+ p = process_tree((struct tree *)obj, p, name);
+ continue;
+ }
+ if (obj->type == blob_type) {
+ p = process_blob((struct blob *)obj, p, name);
+ continue;
+ }
+ die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
+ }
+ while (objects) {
+ /* An object with name "foo\n0000000000000000000000000000000000000000"
+ * can be used confuse downstream git-pack-objects very badly.
+ */
+ const char *ep = strchr(objects->name, '\n');
+ if (ep) {
+ printf("%s %.*s\n", sha1_to_hex(objects->item->sha1),
+ (int) (ep - objects->name),
+ objects->name);
+ }
+ else
+ printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
+ objects = objects->next;
+ }
+}
+
+static void mark_blob_uninteresting(struct blob *blob)
+{
+ if (!blob_objects)
+ return;
+ if (blob->object.flags & UNINTERESTING)
+ return;
+ blob->object.flags |= UNINTERESTING;
+}
+
+static void mark_tree_uninteresting(struct tree *tree)
+{
+ struct object *obj = &tree->object;
+ struct tree_entry_list *entry;
+
+ if (!tree_objects)
+ return;
+ if (obj->flags & UNINTERESTING)
+ return;
+ obj->flags |= UNINTERESTING;
+ if (!has_sha1_file(obj->sha1))
+ return;
+ if (parse_tree(tree) < 0)
+ die("bad tree %s", sha1_to_hex(obj->sha1));
+ entry = tree->entries;
+ tree->entries = NULL;
+ while (entry) {
+ struct tree_entry_list *next = entry->next;
+ if (entry->directory)
+ mark_tree_uninteresting(entry->item.tree);
+ else
+ mark_blob_uninteresting(entry->item.blob);
+ free(entry);
+ entry = next;
+ }
+}
+
+static void mark_parents_uninteresting(struct commit *commit)
+{
+ struct commit_list *parents = commit->parents;
+
+ while (parents) {
+ struct commit *commit = parents->item;
+ commit->object.flags |= UNINTERESTING;
+
+ /*
+ * Normally we haven't parsed the parent
+ * yet, so we won't have a parent of a parent
+ * here. However, it may turn out that we've
+ * reached this commit some other way (where it
+ * wasn't uninteresting), in which case we need
+ * to mark its parents recursively too..
+ */
+ if (commit->parents)
+ mark_parents_uninteresting(commit);
+
+ /*
+ * A missing commit is ok iff its parent is marked
+ * uninteresting.
+ *
+ * We just mark such a thing parsed, so that when
+ * it is popped next time around, we won't be trying
+ * to parse it and get an error.
+ */
+ if (!has_sha1_file(commit->object.sha1))
+ commit->object.parsed = 1;
+ parents = parents->next;
+ }
+}
+
+static int everybody_uninteresting(struct commit_list *orig)
+{
+ struct commit_list *list = orig;
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (commit->object.flags & UNINTERESTING)
+ continue;
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * This is a truly stupid algorithm, but it's only
+ * used for bisection, and we just don't care enough.
+ *
+ * We care just barely enough to avoid recursing for
+ * non-merge entries.
+ */
+static int count_distance(struct commit_list *entry)
+{
+ int nr = 0;
+
+ while (entry) {
+ struct commit *commit = entry->item;
+ struct commit_list *p;
+
+ if (commit->object.flags & (UNINTERESTING | COUNTED))
+ break;
+ nr++;
+ commit->object.flags |= COUNTED;
+ p = commit->parents;
+ entry = p;
+ if (p) {
+ p = p->next;
+ while (p) {
+ nr += count_distance(p);
+ p = p->next;
+ }
+ }
+ }
+ return nr;
+}
+
+static void clear_distance(struct commit_list *list)
+{
+ while (list) {
+ struct commit *commit = list->item;
+ commit->object.flags &= ~COUNTED;
+ list = list->next;
+ }
+}
+
+static struct commit_list *find_bisection(struct commit_list *list)
+{
+ int nr, closest;
+ struct commit_list *p, *best;
+
+ nr = 0;
+ p = list;
+ while (p) {
+ nr++;
+ p = p->next;
+ }
+ closest = 0;
+ best = list;
+
+ p = list;
+ while (p) {
+ int distance = count_distance(p);
+ clear_distance(list);
+ if (nr - distance < distance)
+ distance = nr - distance;
+ if (distance > closest) {
+ best = p;
+ closest = distance;
+ }
+ p = p->next;
+ }
+ if (best)
+ best->next = NULL;
+ return best;
+}
+
+static void mark_edges_uninteresting(struct commit_list *list)
+{
+ for ( ; list; list = list->next) {
+ struct commit_list *parents = list->item->parents;
+
+ for ( ; parents; parents = parents->next) {
+ struct commit *commit = parents->item;
+ if (commit->object.flags & UNINTERESTING)
+ mark_tree_uninteresting(commit->tree);
+ }
+ }
+}
+
+static int is_different = 0;
+
+static void file_add_remove(struct diff_options *options,
+ int addremove, unsigned mode,
+ const unsigned char *sha1,
+ const char *base, const char *path)
+{
+ is_different = 1;
+}
+
+static void file_change(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *base, const char *path)
+{
+ is_different = 1;
+}
+
+static struct diff_options diff_opt = {
+ .recursive = 1,
+ .add_remove = file_add_remove,
+ .change = file_change,
+};
+
+static int same_tree(struct tree *t1, struct tree *t2)
+{
+ is_different = 0;
+ if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+ return 0;
+ return !is_different;
+}
+
+static int same_tree_as_empty(struct tree *t1)
+{
+ int retval;
+ void *tree;
+ struct tree_desc empty, real;
+
+ if (!t1)
+ return 0;
+
+ tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+ if (!tree)
+ return 0;
+ real.buf = tree;
+
+ empty.buf = "";
+ empty.size = 0;
+
+ is_different = 0;
+ retval = diff_tree(&empty, &real, "", &diff_opt);
+ free(tree);
+
+ return retval >= 0 && !is_different;
+}
+
+static struct commit *try_to_simplify_merge(struct commit *commit, struct commit_list *parent)
+{
+ if (!commit->tree)
+ return NULL;
+
+ while (parent) {
+ struct commit *p = parent->item;
+ parent = parent->next;
+ parse_commit(p);
+ if (!p->tree)
+ continue;
+ if (same_tree(commit->tree, p->tree))
+ return p;
+ }
+ return NULL;
+}
+
+static void add_parents_to_list(struct commit *commit, struct commit_list **list)
+{
+ struct commit_list *parent = commit->parents;
+
+ /*
+ * If the commit is uninteresting, don't try to
+ * prune parents - we want the maximal uninteresting
+ * set.
+ *
+ * Normally we haven't parsed the parent
+ * yet, so we won't have a parent of a parent
+ * here. However, it may turn out that we've
+ * reached this commit some other way (where it
+ * wasn't uninteresting), in which case we need
+ * to mark its parents recursively too..
+ */
+ if (commit->object.flags & UNINTERESTING) {
+ while (parent) {
+ struct commit *p = parent->item;
+ parent = parent->next;
+ parse_commit(p);
+ p->object.flags |= UNINTERESTING;
+ if (p->parents)
+ mark_parents_uninteresting(p);
+ if (p->object.flags & SEEN)
+ continue;
+ p->object.flags |= SEEN;
+ insert_by_date(p, list);
+ }
+ return;
+ }
+
+ /*
+ * Ok, the commit wasn't uninteresting. If it
+ * is a merge, try to find the parent that has
+ * no differences in the path set if one exists.
+ */
+ if (paths && parent && parent->next) {
+ struct commit *preferred;
+
+ preferred = try_to_simplify_merge(commit, parent);
+ if (preferred) {
+ parent->item = preferred;
+ parent->next = NULL;
+ }
+ }
+
+ while (parent) {
+ struct commit *p = parent->item;
+
+ parent = parent->next;
+
+ parse_commit(p);
+ if (p->object.flags & SEEN)
+ continue;
+ p->object.flags |= SEEN;
+ insert_by_date(p, list);
+ }
+}
+
+static void compress_list(struct commit_list *list)
+{
+ while (list) {
+ struct commit *commit = list->item;
+ struct commit_list *parent = commit->parents;
+ list = list->next;
+
+ if (!parent) {
+ if (!same_tree_as_empty(commit->tree))
+ commit->object.flags |= TREECHANGE;
+ continue;
+ }
+
+ /*
+ * Exactly one parent? Check if it leaves the tree
+ * unchanged
+ */
+ if (!parent->next) {
+ struct tree *t1 = commit->tree;
+ struct tree *t2 = parent->item->tree;
+ if (!t1 || !t2 || same_tree(t1, t2))
+ continue;
+ }
+ commit->object.flags |= TREECHANGE;
+ }
+}
+
+static struct commit_list *limit_list(struct commit_list *list)
+{
+ struct commit_list *newlist = NULL;
+ struct commit_list **p = &newlist;
+ while (list) {
+ struct commit_list *entry = list;
+ struct commit *commit = list->item;
+ struct object *obj = &commit->object;
+
+ list = list->next;
+ free(entry);
+
+ if (max_age != -1 && (commit->date < max_age))
+ obj->flags |= UNINTERESTING;
+ if (unpacked && has_sha1_pack(obj->sha1))
+ obj->flags |= UNINTERESTING;
+ add_parents_to_list(commit, &list);
+ if (obj->flags & UNINTERESTING) {
+ mark_parents_uninteresting(commit);
+ if (everybody_uninteresting(list))
+ break;
+ continue;
+ }
+ if (min_age != -1 && (commit->date > min_age))
+ continue;
+ p = &commit_list_insert(commit, p)->next;
+ }
+ if (tree_objects)
+ mark_edges_uninteresting(newlist);
+ if (paths && dense)
+ compress_list(newlist);
+ if (bisect_list)
+ newlist = find_bisection(newlist);
+ return newlist;
+}
+
+static void add_pending_object(struct object *obj, const char *name)
+{
+ add_object(obj, &pending_objects, name);
+}
+
+static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
+{
+ struct object *object;
+
+ object = parse_object(sha1);
+ if (!object)
+ die("bad object %s", name);
+
+ /*
+ * Tag object? Look what it points to..
+ */
+ while (object->type == tag_type) {
+ struct tag *tag = (struct tag *) object;
+ object->flags |= flags;
+ if (tag_objects && !(object->flags & UNINTERESTING))
+ add_pending_object(object, tag->tag);
+ object = parse_object(tag->tagged->sha1);
+ if (!object)
+ die("bad object %s", sha1_to_hex(tag->tagged->sha1));
+ }
+
+ /*
+ * Commit object? Just return it, we'll do all the complex
+ * reachability crud.
+ */
+ if (object->type == commit_type) {
+ struct commit *commit = (struct commit *)object;
+ object->flags |= flags;
+ if (parse_commit(commit) < 0)
+ die("unable to parse commit %s", name);
+ if (flags & UNINTERESTING)
+ mark_parents_uninteresting(commit);
+ return commit;
+ }
+
+ /*
+ * Tree object? Either mark it uniniteresting, or add it
+ * to the list of objects to look at later..
+ */
+ if (object->type == tree_type) {
+ struct tree *tree = (struct tree *)object;
+ if (!tree_objects)
+ return NULL;
+ if (flags & UNINTERESTING) {
+ mark_tree_uninteresting(tree);
+ return NULL;
+ }
+ add_pending_object(object, "");
+ return NULL;
+ }
+
+ /*
+ * Blob object? You know the drill by now..
+ */
+ if (object->type == blob_type) {
+ struct blob *blob = (struct blob *)object;
+ if (!blob_objects)
+ return NULL;
+ if (flags & UNINTERESTING) {
+ mark_blob_uninteresting(blob);
+ return NULL;
+ }
+ add_pending_object(object, "");
+ return NULL;
+ }
+ die("%s is unknown object", name);
+}
+
+static void handle_one_commit(struct commit *com, struct commit_list **lst)
+{
+ if (!com || com->object.flags & SEEN)
+ return;
+ com->object.flags |= SEEN;
+ commit_list_insert(com, lst);
+}
+
+/* for_each_ref() callback does not allow user data -- Yuck. */
+static struct commit_list **global_lst;
+
+static int include_one_commit(const char *path, const unsigned char *sha1)
+{
+ struct commit *com = get_commit_reference(path, sha1, 0);
+ handle_one_commit(com, global_lst);
+ return 0;
+}
+
+static void handle_all(struct commit_list **lst)
+{
+ global_lst = lst;
+ for_each_ref(include_one_commit);
+ global_lst = NULL;
+}
+
+int main(int argc, const char **argv)
+{
+ const char *prefix = setup_git_directory();
+ struct commit_list *list = NULL;
+ int i, limited = 0;
+
+ for (i = 1 ; i < argc; i++) {
+ int flags;
+ const char *arg = argv[i];
+ char *dotdot;
+ struct commit *commit;
+ unsigned char sha1[20];
+
+ if (!strncmp(arg, "--max-count=", 12)) {
+ max_count = atoi(arg + 12);
+ continue;
+ }
+ if (!strncmp(arg, "--max-age=", 10)) {
+ max_age = atoi(arg + 10);
+ limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--min-age=", 10)) {
+ min_age = atoi(arg + 10);
+ limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--header")) {
+ verbose_header = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--pretty", 8)) {
+ commit_format = get_commit_format(arg+8);
+ verbose_header = 1;
+ hdr_termination = '\n';
+ if (commit_format == CMIT_FMT_ONELINE)
+ commit_prefix = "";
+ else
+ commit_prefix = "commit ";
+ continue;
+ }
+ if (!strncmp(arg, "--no-merges", 11)) {
+ no_merges = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--parents")) {
+ show_parents = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--bisect")) {
+ bisect_list = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ handle_all(&list);
+ continue;
+ }
+ if (!strcmp(arg, "--objects")) {
+ tag_objects = 1;
+ tree_objects = 1;
+ blob_objects = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--unpacked")) {
+ unpacked = 1;
+ limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--merge-order")) {
+ merge_order = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--show-breaks")) {
+ show_breaks = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--topo-order")) {
+ topo_order = 1;
+ limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--dense")) {
+ dense = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--sparse")) {
+ dense = 0;
+ continue;
+ }
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+
+ if (show_breaks && !merge_order)
+ usage(rev_list_usage);
+
+ flags = 0;
+ dotdot = strstr(arg, "..");
+ if (dotdot) {
+ unsigned char from_sha1[20];
+ char *next = dotdot + 2;
+ *dotdot = 0;
+ if (!*next)
+ next = "HEAD";
+ if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
+ struct commit *exclude;
+ struct commit *include;
+
+ exclude = get_commit_reference(arg, from_sha1, UNINTERESTING);
+ include = get_commit_reference(next, sha1, 0);
+ if (!exclude || !include)
+ die("Invalid revision range %s..%s", arg, next);
+ limited = 1;
+ handle_one_commit(exclude, &list);
+ handle_one_commit(include, &list);
+ continue;
+ }
+ *dotdot = '.';
+ }
+ if (*arg == '^') {
+ flags = UNINTERESTING;
+ arg++;
+ limited = 1;
+ }
+ if (get_sha1(arg, sha1) < 0)
+ break;
+ commit = get_commit_reference(arg, sha1, flags);
+ handle_one_commit(commit, &list);
+ }
+
+ if (!list)
+ usage(rev_list_usage);
+
+ paths = get_pathspec(prefix, argv + i);
+ if (paths) {
+ limited = 1;
+ diff_tree_setup_paths(paths);
+ }
+
+ save_commit_buffer = verbose_header;
+ track_object_refs = 0;
+
+ if (!merge_order) {
+ sort_by_date(&list);
+ if (list && !limited && max_count == 1 &&
+ !tag_objects && !tree_objects && !blob_objects) {
+ show_commit(list->item);
+ return 0;
+ }
+ if (limited)
+ list = limit_list(list);
+ if (topo_order)
+ sort_in_topological_order(&list);
+ show_commit_list(list);
+ } else {
+#ifndef NO_OPENSSL
+ if (sort_list_in_merge_order(list, &process_commit)) {
+ die("merge order sort failed\n");
+ }
+#else
+ die("merge order sort unsupported, OpenSSL not linked");
+#endif
+ }
+
+ return 0;
+}
diff --git a/rev-parse.c b/rev-parse.c
new file mode 100644
index 000000000..bb4949ad7
--- /dev/null
+++ b/rev-parse.c
@@ -0,0 +1,291 @@
+/*
+ * rev-parse.c
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "quote.h"
+
+#define DO_REVS 1
+#define DO_NOREV 2
+#define DO_FLAGS 4
+#define DO_NONFLAGS 8
+static int filter = ~0;
+
+static char *def = NULL;
+
+#define NORMAL 0
+#define REVERSED 1
+static int show_type = NORMAL;
+static int symbolic = 0;
+static int output_sq = 0;
+
+static int revs_count = 0;
+
+/*
+ * Some arguments are relevant "revision" arguments,
+ * others are about output format or other details.
+ * This sorts it all out.
+ */
+static int is_rev_argument(const char *arg)
+{
+ static const char *rev_args[] = {
+ "--all",
+ "--bisect",
+ "--dense",
+ "--header",
+ "--max-age=",
+ "--max-count=",
+ "--merge-order",
+ "--min-age=",
+ "--no-merges",
+ "--objects",
+ "--parents",
+ "--pretty",
+ "--show-breaks",
+ "--sparse",
+ "--topo-order",
+ "--unpacked",
+ NULL
+ };
+ const char **p = rev_args;
+
+ for (;;) {
+ const char *str = *p++;
+ int len;
+ if (!str)
+ return 0;
+ len = strlen(str);
+ if (!strcmp(arg, str) ||
+ (str[len-1] == '=' && !strncmp(arg, str, len)))
+ return 1;
+ }
+}
+
+/* Output argument as a string, either SQ or normal */
+static void show(const char *arg)
+{
+ if (output_sq) {
+ int sq = '\'', ch;
+
+ putchar(sq);
+ while ((ch = *arg++)) {
+ if (ch == sq)
+ fputs("'\\'", stdout);
+ putchar(ch);
+ }
+ putchar(sq);
+ putchar(' ');
+ }
+ else
+ puts(arg);
+}
+
+/* Output a revision, only if filter allows it */
+static void show_rev(int type, const unsigned char *sha1, const char *name)
+{
+ if (!(filter & DO_REVS))
+ return;
+ def = NULL;
+ revs_count++;
+
+ if (type != show_type)
+ putchar('^');
+ if (symbolic && name)
+ show(name);
+ else
+ show(sha1_to_hex(sha1));
+}
+
+/* Output a flag, only if filter allows it. */
+static void show_flag(char *arg)
+{
+ if (!(filter & DO_FLAGS))
+ return;
+ if (filter & (is_rev_argument(arg) ? DO_REVS : DO_NOREV))
+ show(arg);
+}
+
+static void show_default(void)
+{
+ char *s = def;
+
+ if (s) {
+ unsigned char sha1[20];
+
+ def = NULL;
+ if (!get_sha1(s, sha1)) {
+ show_rev(NORMAL, sha1, s);
+ return;
+ }
+ }
+}
+
+static int show_reference(const char *refname, const unsigned char *sha1)
+{
+ show_rev(NORMAL, sha1, refname);
+ return 0;
+}
+
+static void show_datestring(const char *flag, const char *datestr)
+{
+ static char buffer[100];
+
+ /* date handling requires both flags and revs */
+ if ((filter & (DO_FLAGS | DO_REVS)) != (DO_FLAGS | DO_REVS))
+ return;
+ snprintf(buffer, sizeof(buffer), "%s%lu", flag, approxidate(datestr));
+ show(buffer);
+}
+
+static void show_file(const char *arg)
+{
+ show_default();
+ if ((filter & (DO_NONFLAGS|DO_NOREV)) == (DO_NONFLAGS|DO_NOREV))
+ show(arg);
+}
+
+int main(int argc, char **argv)
+{
+ int i, as_is = 0, verify = 0;
+ unsigned char sha1[20];
+ const char *prefix = setup_git_directory();
+
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+ char *dotdot;
+
+ if (as_is) {
+ show_file(arg);
+ continue;
+ }
+ if (*arg == '-') {
+ if (!strcmp(arg, "--")) {
+ as_is = 1;
+ /* Pass on the "--" if we show anything but files.. */
+ if (filter & (DO_FLAGS | DO_REVS))
+ show_file(arg);
+ continue;
+ }
+ if (!strcmp(arg, "--default")) {
+ def = argv[i+1];
+ i++;
+ continue;
+ }
+ if (!strcmp(arg, "--revs-only")) {
+ filter &= ~DO_NOREV;
+ continue;
+ }
+ if (!strcmp(arg, "--no-revs")) {
+ filter &= ~DO_REVS;
+ continue;
+ }
+ if (!strcmp(arg, "--flags")) {
+ filter &= ~DO_NONFLAGS;
+ continue;
+ }
+ if (!strcmp(arg, "--no-flags")) {
+ filter &= ~DO_FLAGS;
+ continue;
+ }
+ if (!strcmp(arg, "--verify")) {
+ filter &= ~(DO_FLAGS|DO_NOREV);
+ verify = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--sq")) {
+ output_sq = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--not")) {
+ show_type ^= REVERSED;
+ continue;
+ }
+ if (!strcmp(arg, "--symbolic")) {
+ symbolic = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ for_each_ref(show_reference);
+ continue;
+ }
+ if (!strcmp(arg, "--show-prefix")) {
+ if (prefix)
+ puts(prefix);
+ continue;
+ }
+ if (!strcmp(arg, "--git-dir")) {
+ const char *gitdir = getenv(GIT_DIR_ENVIRONMENT);
+ static char cwd[PATH_MAX];
+ if (gitdir) {
+ puts(gitdir);
+ continue;
+ }
+ if (!prefix) {
+ puts(".git");
+ continue;
+ }
+ if (!getcwd(cwd, PATH_MAX))
+ die("unable to get current working directory");
+ printf("%s/.git\n", cwd);
+ continue;
+ }
+ if (!strncmp(arg, "--since=", 8)) {
+ show_datestring("--max-age=", arg+8);
+ continue;
+ }
+ if (!strncmp(arg, "--after=", 8)) {
+ show_datestring("--max-age=", arg+8);
+ continue;
+ }
+ if (!strncmp(arg, "--before=", 9)) {
+ show_datestring("--min-age=", arg+9);
+ continue;
+ }
+ if (!strncmp(arg, "--until=", 8)) {
+ show_datestring("--min-age=", arg+8);
+ continue;
+ }
+ if (verify)
+ die("Needed a single revision");
+ show_flag(arg);
+ continue;
+ }
+
+ /* Not a flag argument */
+ dotdot = strstr(arg, "..");
+ if (dotdot) {
+ unsigned char end[20];
+ char *n = dotdot+2;
+ *dotdot = 0;
+ if (!get_sha1(arg, sha1)) {
+ if (!*n)
+ n = "HEAD";
+ if (!get_sha1(n, end)) {
+ show_rev(NORMAL, end, n);
+ show_rev(REVERSED, sha1, arg);
+ continue;
+ }
+ }
+ *dotdot = '.';
+ }
+ if (!get_sha1(arg, sha1)) {
+ show_rev(NORMAL, sha1, arg);
+ continue;
+ }
+ if (*arg == '^' && !get_sha1(arg+1, sha1)) {
+ show_rev(REVERSED, sha1, arg+1);
+ continue;
+ }
+ if (verify)
+ die("Needed a single revision");
+ as_is = 1;
+ show_file(arg);
+ }
+ show_default();
+ if (verify && revs_count != 1)
+ die("Needed a single revision");
+ return 0;
+}
diff --git a/rsh.c b/rsh.c
new file mode 100644
index 000000000..d66526941
--- /dev/null
+++ b/rsh.c
@@ -0,0 +1,112 @@
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include "rsh.h"
+#include "quote.h"
+#include "cache.h"
+
+#define COMMAND_SIZE 4096
+
+/*
+ * Append a string to a string buffer, with or without shell quoting.
+ * Return true if the buffer overflowed.
+ */
+static int add_to_string(char **ptrp, int *sizep, const char *str, int quote)
+{
+ char *p = *ptrp;
+ int size = *sizep;
+ int oc;
+ int err = 0;
+
+ if ( quote ) {
+ oc = sq_quote_buf(p, size, str);
+ } else {
+ oc = strlen(str);
+ memcpy(p, str, (oc >= size) ? size-1 : oc);
+ }
+
+ if ( oc >= size ) {
+ err = 1;
+ oc = size-1;
+ }
+
+ *ptrp += oc;
+ **ptrp = '\0';
+ *sizep -= oc;
+ return err;
+}
+
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
+ char *url, int rmt_argc, char **rmt_argv)
+{
+ char *host;
+ char *path;
+ int sv[2];
+ char command[COMMAND_SIZE];
+ char *posn;
+ int sizen;
+ int of;
+ int i;
+
+ if (!strcmp(url, "-")) {
+ *fd_in = 0;
+ *fd_out = 1;
+ return 0;
+ }
+
+ host = strstr(url, "//");
+ if (host) {
+ host += 2;
+ path = strchr(host, '/');
+ } else {
+ host = url;
+ path = strchr(host, ':');
+ if (path)
+ *(path++) = '\0';
+ }
+ if (!path) {
+ return error("Bad URL: %s", url);
+ }
+ /* $GIT_RSH <host> "env GIT_DIR=<path> <remote_prog> <args...>" */
+ sizen = COMMAND_SIZE;
+ posn = command;
+ of = 0;
+ of |= add_to_string(&posn, &sizen, "env ", 0);
+ of |= add_to_string(&posn, &sizen, GIT_DIR_ENVIRONMENT "=", 0);
+ of |= add_to_string(&posn, &sizen, path, 1);
+ of |= add_to_string(&posn, &sizen, " ", 0);
+ of |= add_to_string(&posn, &sizen, remote_prog, 1);
+
+ for ( i = 0 ; i < rmt_argc ; i++ ) {
+ of |= add_to_string(&posn, &sizen, " ", 0);
+ of |= add_to_string(&posn, &sizen, rmt_argv[i], 1);
+ }
+
+ of |= add_to_string(&posn, &sizen, " -", 0);
+
+ if ( of )
+ return error("Command line too long");
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv))
+ return error("Couldn't create socket");
+
+ if (!fork()) {
+ const char *ssh, *ssh_basename;
+ ssh = getenv("GIT_SSH");
+ if (!ssh) ssh = "ssh";
+ ssh_basename = strrchr(ssh, '/');
+ if (!ssh_basename)
+ ssh_basename = ssh;
+ else
+ ssh_basename++;
+ close(sv[1]);
+ dup2(sv[0], 0);
+ dup2(sv[0], 1);
+ execlp(ssh, ssh_basename, host, command, NULL);
+ }
+ close(sv[0]);
+ *fd_in = sv[1];
+ *fd_out = sv[1];
+ return 0;
+}
diff --git a/rsh.h b/rsh.h
new file mode 100644
index 000000000..3b4194239
--- /dev/null
+++ b/rsh.h
@@ -0,0 +1,7 @@
+#ifndef RSH_H
+#define RSH_H
+
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
+ char *url, int rmt_argc, char **rmt_argv);
+
+#endif
diff --git a/run-command.c b/run-command.c
new file mode 100644
index 000000000..5787a5095
--- /dev/null
+++ b/run-command.c
@@ -0,0 +1,58 @@
+#include "cache.h"
+#include "run-command.h"
+#include <sys/wait.h>
+
+int run_command_v(int argc, char **argv)
+{
+ pid_t pid = fork();
+
+ if (pid < 0)
+ return -ERR_RUN_COMMAND_FORK;
+ if (!pid) {
+ execvp(argv[0], (char *const*) argv);
+ die("exec %s failed.", argv[0]);
+ }
+ for (;;) {
+ int status, code;
+ int retval = waitpid(pid, &status, 0);
+
+ if (retval < 0) {
+ if (errno == EINTR)
+ continue;
+ error("waitpid failed (%s)", strerror(retval));
+ return -ERR_RUN_COMMAND_WAITPID;
+ }
+ if (retval != pid)
+ return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
+ if (WIFSIGNALED(status))
+ return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
+
+ if (!WIFEXITED(status))
+ return -ERR_RUN_COMMAND_WAITPID_NOEXIT;
+ code = WEXITSTATUS(status);
+ if (code)
+ return -code;
+ return 0;
+ }
+}
+
+int run_command(const char *cmd, ...)
+{
+ int argc;
+ char *argv[MAX_RUN_COMMAND_ARGS];
+ const char *arg;
+ va_list param;
+
+ va_start(param, cmd);
+ argv[0] = (char*) cmd;
+ argc = 1;
+ while (argc < MAX_RUN_COMMAND_ARGS) {
+ arg = argv[argc++] = va_arg(param, char *);
+ if (!arg)
+ break;
+ }
+ va_end(param);
+ if (MAX_RUN_COMMAND_ARGS <= argc)
+ return error("too many args to run %s", cmd);
+ return run_command_v(argc, argv);
+}
diff --git a/run-command.h b/run-command.h
new file mode 100644
index 000000000..5ee097224
--- /dev/null
+++ b/run-command.h
@@ -0,0 +1,17 @@
+#ifndef RUN_COMMAND_H
+#define RUN_COMMAND_H
+
+#define MAX_RUN_COMMAND_ARGS 256
+enum {
+ ERR_RUN_COMMAND_FORK = 10000,
+ ERR_RUN_COMMAND_EXEC,
+ ERR_RUN_COMMAND_WAITPID,
+ ERR_RUN_COMMAND_WAITPID_WRONG_PID,
+ ERR_RUN_COMMAND_WAITPID_SIGNAL,
+ ERR_RUN_COMMAND_WAITPID_NOEXIT,
+};
+
+int run_command_v(int argc, char **argv);
+int run_command(const char *cmd, ...);
+
+#endif
diff --git a/send-pack.c b/send-pack.c
new file mode 100644
index 000000000..3eeb18f7c
--- /dev/null
+++ b/send-pack.c
@@ -0,0 +1,315 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "pkt-line.h"
+
+static const char send_pack_usage[] =
+"git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n"
+" --all and explicit <head> specification are mutually exclusive.";
+static const char *exec = "git-receive-pack";
+static int send_all = 0;
+static int force_update = 0;
+
+static int is_zero_sha1(const unsigned char *sha1)
+{
+ int i;
+
+ for (i = 0; i < 20; i++) {
+ if (*sha1++)
+ return 0;
+ }
+ return 1;
+}
+
+static void exec_pack_objects(void)
+{
+ static char *args[] = {
+ "git-pack-objects",
+ "--stdout",
+ NULL
+ };
+ execvp("git-pack-objects", args);
+ die("git-pack-objects exec failed (%s)", strerror(errno));
+}
+
+static void exec_rev_list(struct ref *refs)
+{
+ static char *args[1000];
+ int i = 0;
+
+ args[i++] = "git-rev-list"; /* 0 */
+ args[i++] = "--objects"; /* 1 */
+ while (refs) {
+ char *buf = malloc(100);
+ if (i > 900)
+ die("git-rev-list environment overflow");
+ if (!is_zero_sha1(refs->old_sha1) &&
+ has_sha1_file(refs->old_sha1)) {
+ args[i++] = buf;
+ snprintf(buf, 50, "^%s", sha1_to_hex(refs->old_sha1));
+ buf += 50;
+ }
+ if (!is_zero_sha1(refs->new_sha1)) {
+ args[i++] = buf;
+ snprintf(buf, 50, "%s", sha1_to_hex(refs->new_sha1));
+ }
+ refs = refs->next;
+ }
+ args[i] = NULL;
+ execvp("git-rev-list", args);
+ die("git-rev-list exec failed (%s)", strerror(errno));
+}
+
+static void rev_list(int fd, struct ref *refs)
+{
+ int pipe_fd[2];
+ pid_t pack_objects_pid;
+
+ if (pipe(pipe_fd) < 0)
+ die("rev-list setup: pipe failed");
+ pack_objects_pid = fork();
+ if (!pack_objects_pid) {
+ dup2(pipe_fd[0], 0);
+ dup2(fd, 1);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ close(fd);
+ exec_pack_objects();
+ die("pack-objects setup failed");
+ }
+ if (pack_objects_pid < 0)
+ die("pack-objects fork failed");
+ dup2(pipe_fd[1], 1);
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ close(fd);
+ exec_rev_list(refs);
+}
+
+static int pack_objects(int fd, struct ref *refs)
+{
+ pid_t rev_list_pid;
+
+ rev_list_pid = fork();
+ if (!rev_list_pid) {
+ rev_list(fd, refs);
+ die("rev-list setup failed");
+ }
+ if (rev_list_pid < 0)
+ die("rev-list fork failed");
+ /*
+ * We don't wait for the rev-list pipeline in the parent:
+ * we end up waiting for the other end instead
+ */
+ return 0;
+}
+
+static void unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+ while (list) {
+ struct commit_list *temp = list;
+ temp->item->object.flags &= ~mark;
+ list = temp->next;
+ free(temp);
+ }
+}
+
+static int ref_newer(const unsigned char *new_sha1,
+ const unsigned char *old_sha1)
+{
+ struct object *o;
+ struct commit *old, *new;
+ struct commit_list *list, *used;
+ int found = 0;
+
+ /* Both new and old must be commit-ish and new is descendant of
+ * old. Otherwise we require --force.
+ */
+ o = deref_tag(parse_object(old_sha1), NULL, 0);
+ if (!o || o->type != commit_type)
+ return 0;
+ old = (struct commit *) o;
+
+ o = deref_tag(parse_object(new_sha1), NULL, 0);
+ if (!o || o->type != commit_type)
+ return 0;
+ new = (struct commit *) o;
+
+ if (parse_commit(new) < 0)
+ return 0;
+
+ used = list = NULL;
+ commit_list_insert(new, &list);
+ while (list) {
+ new = pop_most_recent_commit(&list, 1);
+ commit_list_insert(new, &used);
+ if (new == old) {
+ found = 1;
+ break;
+ }
+ }
+ unmark_and_free(list, 1);
+ unmark_and_free(used, 1);
+ return found;
+}
+
+static struct ref *local_refs, **local_tail;
+static struct ref *remote_refs, **remote_tail;
+
+static int one_local_ref(const char *refname, const unsigned char *sha1)
+{
+ struct ref *ref;
+ int len = strlen(refname) + 1;
+ ref = xcalloc(1, sizeof(*ref) + len);
+ memcpy(ref->new_sha1, sha1, 20);
+ memcpy(ref->name, refname, len);
+ *local_tail = ref;
+ local_tail = &ref->next;
+ return 0;
+}
+
+static void get_local_heads(void)
+{
+ local_tail = &local_refs;
+ for_each_ref(one_local_ref);
+}
+
+static int send_pack(int in, int out, int nr_refspec, char **refspec)
+{
+ struct ref *ref;
+ int new_refs;
+
+ /* No funny business with the matcher */
+ remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, 1);
+ get_local_heads();
+
+ /* match them up */
+ if (!remote_tail)
+ remote_tail = &remote_refs;
+ if (match_refs(local_refs, remote_refs, &remote_tail,
+ nr_refspec, refspec, send_all))
+ return -1;
+ /*
+ * Finally, tell the other end!
+ */
+ new_refs = 0;
+ for (ref = remote_refs; ref; ref = ref->next) {
+ char old_hex[60], *new_hex;
+ if (!ref->peer_ref)
+ continue;
+ if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) {
+ fprintf(stderr, "'%s': up-to-date\n", ref->name);
+ continue;
+ }
+
+ /* This part determines what can overwrite what.
+ * The rules are:
+ *
+ * (0) you can always use --force or +A:B notation to
+ * selectively force individual ref pairs.
+ *
+ * (1) if the old thing does not exist, it is OK.
+ *
+ * (2) if you do not have the old thing, you are not allowed
+ * to overwrite it; you would not know what you are losing
+ * otherwise.
+ *
+ * (3) if both new and old are commit-ish, and new is a
+ * descendant of old, it is OK.
+ */
+
+ if (!force_update &&
+ !is_zero_sha1(ref->old_sha1) &&
+ !ref->force) {
+ if (!has_sha1_file(ref->old_sha1)) {
+ error("remote '%s' object %s does not "
+ "exist on local",
+ ref->name, sha1_to_hex(ref->old_sha1));
+ continue;
+ }
+
+ /* We assume that local is fsck-clean. Otherwise
+ * you _could_ have an old tag which points at
+ * something you do not have, which may or may not
+ * be a commit.
+ */
+ if (!ref_newer(ref->peer_ref->new_sha1,
+ ref->old_sha1)) {
+ error("remote ref '%s' is not a strict "
+ "subset of local ref '%s'.", ref->name,
+ ref->peer_ref->name);
+ continue;
+ }
+ }
+ memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20);
+ if (is_zero_sha1(ref->new_sha1)) {
+ error("cannot happen anymore");
+ continue;
+ }
+ new_refs++;
+ strcpy(old_hex, sha1_to_hex(ref->old_sha1));
+ new_hex = sha1_to_hex(ref->new_sha1);
+ packet_write(out, "%s %s %s", old_hex, new_hex, ref->name);
+ fprintf(stderr, "updating '%s'", ref->name);
+ if (strcmp(ref->name, ref->peer_ref->name))
+ fprintf(stderr, " using '%s'", ref->peer_ref->name);
+ fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex);
+ }
+
+ packet_flush(out);
+ if (new_refs)
+ pack_objects(out, remote_refs);
+ close(out);
+ return 0;
+}
+
+
+int main(int argc, char **argv)
+{
+ int i, nr_heads = 0;
+ char *dest = NULL;
+ char **heads = NULL;
+ int fd[2], ret;
+ pid_t pid;
+
+ argv++;
+ for (i = 1; i < argc; i++, argv++) {
+ char *arg = *argv;
+
+ if (*arg == '-') {
+ if (!strncmp(arg, "--exec=", 7)) {
+ exec = arg + 7;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ send_all = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--force")) {
+ force_update = 1;
+ continue;
+ }
+ usage(send_pack_usage);
+ }
+ if (!dest) {
+ dest = arg;
+ continue;
+ }
+ heads = argv;
+ nr_heads = argc - i;
+ break;
+ }
+ if (!dest)
+ usage(send_pack_usage);
+ if (heads && send_all)
+ usage(send_pack_usage);
+ pid = git_connect(fd, dest, exec);
+ if (pid < 0)
+ return 1;
+ ret = send_pack(fd[0], fd[1], nr_heads, heads);
+ close(fd[0]);
+ close(fd[1]);
+ finish_connect(pid);
+ return ret;
+}
diff --git a/server-info.c b/server-info.c
new file mode 100644
index 000000000..e4006f0b5
--- /dev/null
+++ b/server-info.c
@@ -0,0 +1,534 @@
+#include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "commit.h"
+#include "tag.h"
+
+/* refs */
+static FILE *info_ref_fp;
+
+static int add_info_ref(const char *path, const unsigned char *sha1)
+{
+ struct object *o = parse_object(sha1);
+
+ fprintf(info_ref_fp, "%s %s\n", sha1_to_hex(sha1), path);
+ if (o->type == tag_type) {
+ o = deref_tag(o, path, 0);
+ if (o)
+ fprintf(info_ref_fp, "%s %s^{}\n",
+ sha1_to_hex(o->sha1), path);
+ }
+ return 0;
+}
+
+static int update_info_refs(int force)
+{
+ char *path0 = strdup(git_path("info/refs"));
+ int len = strlen(path0);
+ char *path1 = xmalloc(len + 2);
+
+ strcpy(path1, path0);
+ strcpy(path1 + len, "+");
+
+ safe_create_leading_directories(path0);
+ info_ref_fp = fopen(path1, "w");
+ if (!info_ref_fp)
+ return error("unable to update %s", path0);
+ for_each_ref(add_info_ref);
+ fclose(info_ref_fp);
+ rename(path1, path0);
+ free(path0);
+ free(path1);
+ return 0;
+}
+
+/* packs */
+static struct pack_info {
+ unsigned long latest;
+ struct packed_git *p;
+ int old_num;
+ int new_num;
+ int nr_alloc;
+ int nr_heads;
+ unsigned char (*head)[20];
+ char dep[0]; /* more */
+} **info;
+static int num_pack;
+static const char *objdir;
+static int objdirlen;
+
+static struct object *parse_object_cheap(const unsigned char *sha1)
+{
+ struct object *o;
+
+ if ((o = parse_object(sha1)) == NULL)
+ return NULL;
+ if (o->type == commit_type) {
+ struct commit *commit = (struct commit *)o;
+ free(commit->buffer);
+ commit->buffer = NULL;
+ } else if (o->type == tree_type) {
+ struct tree *tree = (struct tree *)o;
+ struct tree_entry_list *e, *n;
+ for (e = tree->entries; e; e = n) {
+ free(e->name);
+ e->name = NULL;
+ n = e->next;
+ free(e);
+ }
+ tree->entries = NULL;
+ }
+ return o;
+}
+
+static struct pack_info *find_pack_by_name(const char *name)
+{
+ int i;
+ for (i = 0; i < num_pack; i++) {
+ struct packed_git *p = info[i]->p;
+ /* skip "/pack/" after ".git/objects" */
+ if (!strcmp(p->pack_name + objdirlen + 6, name))
+ return info[i];
+ }
+ return NULL;
+}
+
+static struct pack_info *find_pack_by_old_num(int old_num)
+{
+ int i;
+ for (i = 0; i < num_pack; i++)
+ if (info[i]->old_num == old_num)
+ return info[i];
+ return NULL;
+}
+
+static int add_head_def(struct pack_info *this, unsigned char *sha1)
+{
+ if (this->nr_alloc <= this->nr_heads) {
+ this->nr_alloc = alloc_nr(this->nr_alloc);
+ this->head = xrealloc(this->head, this->nr_alloc * 20);
+ }
+ memcpy(this->head[this->nr_heads++], sha1, 20);
+ return 0;
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int parse_pack_def(const char *line, int old_cnt)
+{
+ struct pack_info *i = find_pack_by_name(line + 2);
+ if (i) {
+ i->old_num = old_cnt;
+ return 0;
+ }
+ else {
+ /* The file describes a pack that is no longer here;
+ * dependencies between packs needs to be recalculated.
+ */
+ return 1;
+ }
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int parse_depend_def(char *line)
+{
+ unsigned long num;
+ char *cp, *ep;
+ struct pack_info *this, *that;
+
+ cp = line + 2;
+ num = strtoul(cp, &ep, 10);
+ if (ep == cp)
+ return error("invalid input %s", line);
+ this = find_pack_by_old_num(num);
+ if (!this)
+ return 0;
+ while (ep && *(cp = ep)) {
+ num = strtoul(cp, &ep, 10);
+ if (ep == cp)
+ break;
+ that = find_pack_by_old_num(num);
+ if (!that)
+ /* The pack this one depends on does not
+ * exist; this should not happen because
+ * we write out the list of packs first and
+ * then dependency information, but it means
+ * the file is useless anyway.
+ */
+ return 1;
+ this->dep[that->new_num] = 1;
+ }
+ return 0;
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int parse_head_def(char *line)
+{
+ unsigned char sha1[20];
+ unsigned long num;
+ char *cp, *ep;
+ struct pack_info *this;
+ struct object *o;
+
+ cp = line + 2;
+ num = strtoul(cp, &ep, 10);
+ if (ep == cp || *ep++ != ' ')
+ return error("invalid input ix %s", line);
+ this = find_pack_by_old_num(num);
+ if (!this)
+ return 1; /* You know the drill. */
+ if (get_sha1_hex(ep, sha1) || ep[40] != ' ')
+ return error("invalid input sha1 %s (%s)", line, ep);
+ if ((o = parse_object_cheap(sha1)) == NULL)
+ return error("no such object: %s", line);
+ return add_head_def(this, sha1);
+}
+
+/* Returns non-zero when we detect that the info in the
+ * old file is useless.
+ */
+static int read_pack_info_file(const char *infofile)
+{
+ FILE *fp;
+ char line[1000];
+ int old_cnt = 0;
+
+ fp = fopen(infofile, "r");
+ if (!fp)
+ return 1; /* nonexisting is not an error. */
+
+ while (fgets(line, sizeof(line), fp)) {
+ int len = strlen(line);
+ if (line[len-1] == '\n')
+ line[len-1] = 0;
+
+ switch (line[0]) {
+ case 'P': /* P name */
+ if (parse_pack_def(line, old_cnt++))
+ goto out_stale;
+ break;
+ case 'D': /* D ix dep-ix1 dep-ix2... */
+ if (parse_depend_def(line))
+ goto out_stale;
+ break;
+ case 'T': /* T ix sha1 type */
+ if (parse_head_def(line))
+ goto out_stale;
+ break;
+ default:
+ error("unrecognized: %s", line);
+ break;
+ }
+ }
+ fclose(fp);
+ return 0;
+ out_stale:
+ fclose(fp);
+ return 1;
+}
+
+/* We sort the packs according to the date of the latest commit. That
+ * in turn indicates how young the pack is, and in general we would
+ * want to depend on younger packs.
+ */
+static unsigned long get_latest_commit_date(struct packed_git *p)
+{
+ unsigned char sha1[20];
+ struct object *o;
+ int num = num_packed_objects(p);
+ int i;
+ unsigned long latest = 0;
+
+ for (i = 0; i < num; i++) {
+ if (nth_packed_object_sha1(p, i, sha1))
+ die("corrupt pack file %s?", p->pack_name);
+ if ((o = parse_object_cheap(sha1)) == NULL)
+ die("cannot parse %s", sha1_to_hex(sha1));
+ if (o->type == commit_type) {
+ struct commit *commit = (struct commit *)o;
+ if (latest < commit->date)
+ latest = commit->date;
+ }
+ }
+ return latest;
+}
+
+static int compare_info(const void *a_, const void *b_)
+{
+ struct pack_info * const* a = a_;
+ struct pack_info * const* b = b_;
+
+ if (0 <= (*a)->old_num && 0 <= (*b)->old_num)
+ /* Keep the order in the original */
+ return (*a)->old_num - (*b)->old_num;
+ else if (0 <= (*a)->old_num)
+ /* Only A existed in the original so B is obviously newer */
+ return -1;
+ else if (0 <= (*b)->old_num)
+ /* The other way around. */
+ return 1;
+
+ if ((*a)->latest < (*b)->latest)
+ return -1;
+ else if ((*a)->latest == (*b)->latest)
+ return 0;
+ else
+ return 1;
+}
+
+static void init_pack_info(const char *infofile, int force)
+{
+ struct packed_git *p;
+ int stale;
+ int i = 0;
+ char *dep_temp;
+
+ objdir = get_object_directory();
+ objdirlen = strlen(objdir);
+
+ prepare_packed_git();
+ for (p = packed_git; p; p = p->next) {
+ /* we ignore things on alternate path since they are
+ * not available to the pullers in general.
+ */
+ if (strncmp(p->pack_name, objdir, objdirlen) ||
+ strncmp(p->pack_name + objdirlen, "/pack/", 6))
+ continue;
+ i++;
+ }
+ num_pack = i;
+ info = xcalloc(num_pack, sizeof(struct pack_info *));
+ for (i = 0, p = packed_git; p; p = p->next) {
+ if (strncmp(p->pack_name, objdir, objdirlen) ||
+ p->pack_name[objdirlen] != '/')
+ continue;
+ info[i] = xcalloc(1, sizeof(struct pack_info) + num_pack);
+ info[i]->p = p;
+ info[i]->old_num = -1;
+ i++;
+ }
+
+ if (infofile && !force)
+ stale = read_pack_info_file(infofile);
+ else
+ stale = 1;
+
+ for (i = 0; i < num_pack; i++) {
+ if (stale) {
+ info[i]->old_num = -1;
+ memset(info[i]->dep, 0, num_pack);
+ info[i]->nr_heads = 0;
+ }
+ if (info[i]->old_num < 0)
+ info[i]->latest = get_latest_commit_date(info[i]->p);
+ }
+
+ qsort(info, num_pack, sizeof(info[0]), compare_info);
+ for (i = 0; i < num_pack; i++)
+ info[i]->new_num = i;
+
+ /* we need to fix up the dependency information
+ * for the old ones.
+ */
+ dep_temp = NULL;
+ for (i = 0; i < num_pack; i++) {
+ int old;
+
+ if (info[i]->old_num < 0)
+ continue;
+ if (! dep_temp)
+ dep_temp = xmalloc(num_pack);
+ memset(dep_temp, 0, num_pack);
+ for (old = 0; old < num_pack; old++) {
+ struct pack_info *base;
+ if (!info[i]->dep[old])
+ continue;
+ base = find_pack_by_old_num(old);
+ if (!base)
+ die("internal error renumbering");
+ dep_temp[base->new_num] = 1;
+ }
+ memcpy(info[i]->dep, dep_temp, num_pack);
+ }
+ free(dep_temp);
+}
+
+static void write_pack_info_file(FILE *fp)
+{
+ int i, j;
+ for (i = 0; i < num_pack; i++)
+ fprintf(fp, "P %s\n", info[i]->p->pack_name + objdirlen + 6);
+
+ for (i = 0; i < num_pack; i++) {
+ fprintf(fp, "D %1d", i);
+ for (j = 0; j < num_pack; j++) {
+ if ((i == j) || !(info[i]->dep[j]))
+ continue;
+ fprintf(fp, " %1d", j);
+ }
+ fputc('\n', fp);
+ }
+
+ for (i = 0; i < num_pack; i++) {
+ struct pack_info *this = info[i];
+ for (j = 0; j < this->nr_heads; j++) {
+ struct object *o = lookup_object(this->head[j]);
+ fprintf(fp, "T %1d %s %s\n",
+ i, sha1_to_hex(this->head[j]), o->type);
+ }
+ }
+
+}
+
+#define REFERENCED 01
+#define INTERNAL 02
+#define EMITTED 04
+
+static void show(struct object *o, int pack_ix)
+{
+ /*
+ * We are interested in objects that are not referenced,
+ * and objects that are referenced but not internal.
+ */
+ if (o->flags & EMITTED)
+ return;
+
+ if (!(o->flags & REFERENCED))
+ add_head_def(info[pack_ix], o->sha1);
+ else if ((o->flags & REFERENCED) && !(o->flags & INTERNAL)) {
+ int i;
+
+ /* Which pack contains this object? That is what
+ * pack_ix can depend on. We earlier sorted info
+ * array from youngest to oldest, so try newer packs
+ * first to favor them here.
+ */
+ for (i = num_pack - 1; 0 <= i; i--) {
+ struct packed_git *p = info[i]->p;
+ struct pack_entry ent;
+ if (find_pack_entry_one(o->sha1, &ent, p)) {
+ info[pack_ix]->dep[i] = 1;
+ break;
+ }
+ }
+ }
+ o->flags |= EMITTED;
+}
+
+static void find_pack_info_one(int pack_ix)
+{
+ unsigned char sha1[20];
+ struct object *o;
+ int i;
+ struct packed_git *p = info[pack_ix]->p;
+ int num = num_packed_objects(p);
+
+ /* Scan objects, clear flags from all the edge ones and
+ * internal ones, possibly marked in the previous round.
+ */
+ for (i = 0; i < num; i++) {
+ if (nth_packed_object_sha1(p, i, sha1))
+ die("corrupt pack file %s?", p->pack_name);
+ if ((o = lookup_object(sha1)) == NULL)
+ die("cannot parse %s", sha1_to_hex(sha1));
+ if (o->refs) {
+ struct object_refs *refs = o->refs;
+ int j;
+ for (j = 0; j < refs->count; j++)
+ refs->ref[j]->flags = 0;
+ }
+ o->flags = 0;
+ }
+
+ /* Mark all the internal ones */
+ for (i = 0; i < num; i++) {
+ if (nth_packed_object_sha1(p, i, sha1))
+ die("corrupt pack file %s?", p->pack_name);
+ if ((o = lookup_object(sha1)) == NULL)
+ die("cannot find %s", sha1_to_hex(sha1));
+ if (o->refs) {
+ struct object_refs *refs = o->refs;
+ int j;
+ for (j = 0; j < refs->count; j++)
+ refs->ref[j]->flags |= REFERENCED;
+ }
+ o->flags |= INTERNAL;
+ }
+
+ for (i = 0; i < num; i++) {
+ if (nth_packed_object_sha1(p, i, sha1))
+ die("corrupt pack file %s?", p->pack_name);
+ if ((o = lookup_object(sha1)) == NULL)
+ die("cannot find %s", sha1_to_hex(sha1));
+
+ show(o, pack_ix);
+ if (o->refs) {
+ struct object_refs *refs = o->refs;
+ int j;
+ for (j = 0; j < refs->count; j++)
+ show(refs->ref[j], pack_ix);
+ }
+ }
+
+}
+
+static void find_pack_info(void)
+{
+ int i;
+ for (i = 0; i < num_pack; i++) {
+ /* The packed objects are cast in stone, and a head
+ * in a pack will stay as head, so is the set of missing
+ * objects. If the repo has been reorganized and we
+ * are missing some packs available back then, we have
+ * already discarded the info read from the file, so
+ * we will find (old_num < 0) in that case.
+ */
+ if (0 <= info[i]->old_num)
+ continue;
+ find_pack_info_one(i);
+ }
+}
+
+static int update_info_packs(int force)
+{
+ char infofile[PATH_MAX];
+ char name[PATH_MAX];
+ int namelen;
+ FILE *fp;
+
+ namelen = sprintf(infofile, "%s/info/packs", get_object_directory());
+ strcpy(name, infofile);
+ strcpy(name + namelen, "+");
+
+ init_pack_info(infofile, force);
+ find_pack_info();
+
+ safe_create_leading_directories(name);
+ fp = fopen(name, "w");
+ if (!fp)
+ return error("cannot open %s", name);
+ write_pack_info_file(fp);
+ fclose(fp);
+ rename(name, infofile);
+ return 0;
+}
+
+/* public */
+int update_server_info(int force)
+{
+ /* We would add more dumb-server support files later,
+ * including index of available pack files and their
+ * intended audiences.
+ */
+ int errs = 0;
+
+ errs = errs | update_info_refs(force);
+ errs = errs | update_info_packs(force);
+
+ return errs;
+}
diff --git a/setup.c b/setup.c
new file mode 100644
index 000000000..c487d7eb9
--- /dev/null
+++ b/setup.c
@@ -0,0 +1,129 @@
+#include "cache.h"
+
+const char *prefix_path(const char *prefix, int len, const char *path)
+{
+ const char *orig = path;
+ for (;;) {
+ char c;
+ if (*path != '.')
+ break;
+ c = path[1];
+ /* "." */
+ if (!c) {
+ path++;
+ break;
+ }
+ /* "./" */
+ if (c == '/') {
+ path += 2;
+ continue;
+ }
+ if (c != '.')
+ break;
+ c = path[2];
+ if (!c)
+ path += 2;
+ else if (c == '/')
+ path += 3;
+ else
+ break;
+ /* ".." and "../" */
+ /* Remove last component of the prefix */
+ do {
+ if (!len)
+ die("'%s' is outside repository", orig);
+ len--;
+ } while (len && prefix[len-1] != '/');
+ continue;
+ }
+ if (len) {
+ int speclen = strlen(path);
+ char *n = xmalloc(speclen + len + 1);
+
+ memcpy(n, prefix, len);
+ memcpy(n + len, path, speclen+1);
+ path = n;
+ }
+ return path;
+}
+
+const char **get_pathspec(const char *prefix, const char **pathspec)
+{
+ const char *entry = *pathspec;
+ const char **p;
+ int prefixlen;
+
+ if (!prefix && !entry)
+ return NULL;
+
+ if (!entry) {
+ static const char *spec[2];
+ spec[0] = prefix;
+ spec[1] = NULL;
+ return spec;
+ }
+
+ /* Otherwise we have to re-write the entries.. */
+ p = pathspec;
+ prefixlen = prefix ? strlen(prefix) : 0;
+ do {
+ *p = prefix_path(prefix, prefixlen, entry);
+ } while ((entry = *++p) != NULL);
+ return (const char **) pathspec;
+}
+
+/*
+ * Test it it looks like we're at the top
+ * level git directory. We want to see a
+ *
+ * - either a .git/objects/ directory _or_ the proper
+ * GIT_OBJECT_DIRECTORY environment variable
+ * - a refs/ directory under ".git"
+ * - either a HEAD symlink or a HEAD file that is formatted as
+ * a proper "ref:".
+ */
+static int is_toplevel_directory(void)
+{
+ if (access(".git/refs/", X_OK) ||
+ access(getenv(DB_ENVIRONMENT) ?
+ getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
+ validate_symref(".git/HEAD"))
+ return 0;
+ return 1;
+}
+
+const char *setup_git_directory(void)
+{
+ static char cwd[PATH_MAX+1];
+ int len, offset;
+
+ /*
+ * If GIT_DIR is set explicitly, we're not going
+ * to do any discovery
+ */
+ if (getenv(GIT_DIR_ENVIRONMENT))
+ return NULL;
+
+ if (!getcwd(cwd, sizeof(cwd)) || cwd[0] != '/')
+ die("Unable to read current working directory");
+
+ offset = len = strlen(cwd);
+ for (;;) {
+ if (is_toplevel_directory())
+ break;
+ chdir("..");
+ do {
+ if (!offset)
+ die("Not a git repository");
+ } while (cwd[--offset] != '/');
+ }
+
+ if (offset == len)
+ return NULL;
+
+ /* Make "offset" point to past the '/', and add a '/' at the end */
+ offset++;
+ cwd[len++] = '/';
+ cwd[len] = 0;
+ return cwd + offset;
+}
diff --git a/sha1_file.c b/sha1_file.c
new file mode 100644
index 000000000..82a01887c
--- /dev/null
+++ b/sha1_file.c
@@ -0,0 +1,1579 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ *
+ * This handles basic git sha1 object files - packing, unpacking,
+ * creation etc.
+ */
+#include <sys/types.h>
+#include <dirent.h>
+#include "cache.h"
+#include "delta.h"
+#include "pack.h"
+
+#ifndef O_NOATIME
+#if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
+#define O_NOATIME 01000000
+#else
+#define O_NOATIME 0
+#endif
+#endif
+
+const unsigned char null_sha1[20] = { 0, };
+
+static unsigned int sha1_file_open_flag = O_NOATIME;
+
+static unsigned hexval(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return ~0;
+}
+
+int get_sha1_hex(const char *hex, unsigned char *sha1)
+{
+ int i;
+ for (i = 0; i < 20; i++) {
+ unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+ if (val & ~0xff)
+ return -1;
+ *sha1++ = val;
+ hex += 2;
+ }
+ return 0;
+}
+
+int safe_create_leading_directories(char *path)
+{
+ char *pos = path;
+ if (*pos == '/')
+ pos++;
+
+ while (pos) {
+ pos = strchr(pos, '/');
+ if (!pos)
+ break;
+ *pos = 0;
+ if (mkdir(path, 0777) < 0)
+ if (errno != EEXIST) {
+ *pos = '/';
+ return -1;
+ }
+ *pos++ = '/';
+ }
+ return 0;
+}
+
+char * sha1_to_hex(const unsigned char *sha1)
+{
+ static char buffer[50];
+ static const char hex[] = "0123456789abcdef";
+ char *buf = buffer;
+ int i;
+
+ for (i = 0; i < 20; i++) {
+ unsigned int val = *sha1++;
+ *buf++ = hex[val >> 4];
+ *buf++ = hex[val & 0xf];
+ }
+ return buffer;
+}
+
+static void fill_sha1_path(char *pathbuf, const unsigned char *sha1)
+{
+ int i;
+ for (i = 0; i < 20; i++) {
+ static char hex[] = "0123456789abcdef";
+ unsigned int val = sha1[i];
+ char *pos = pathbuf + i*2 + (i > 0);
+ *pos++ = hex[val >> 4];
+ *pos = hex[val & 0xf];
+ }
+}
+
+/*
+ * NOTE! This returns a statically allocated buffer, so you have to be
+ * careful about using it. Do a "strdup()" if you need to save the
+ * filename.
+ *
+ * Also note that this returns the location for creating. Reading
+ * SHA1 file can happen from any alternate directory listed in the
+ * DB_ENVIRONMENT environment variable if it is not found in
+ * the primary object database.
+ */
+char *sha1_file_name(const unsigned char *sha1)
+{
+ static char *name, *base;
+
+ if (!base) {
+ const char *sha1_file_directory = get_object_directory();
+ int len = strlen(sha1_file_directory);
+ base = xmalloc(len + 60);
+ memcpy(base, sha1_file_directory, len);
+ memset(base+len, 0, 60);
+ base[len] = '/';
+ base[len+3] = '/';
+ name = base + len + 1;
+ }
+ fill_sha1_path(name, sha1);
+ return base;
+}
+
+char *sha1_pack_name(const unsigned char *sha1)
+{
+ static const char hex[] = "0123456789abcdef";
+ static char *name, *base, *buf;
+ int i;
+
+ if (!base) {
+ const char *sha1_file_directory = get_object_directory();
+ int len = strlen(sha1_file_directory);
+ base = xmalloc(len + 60);
+ sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
+ name = base + len + 11;
+ }
+
+ buf = name;
+
+ for (i = 0; i < 20; i++) {
+ unsigned int val = *sha1++;
+ *buf++ = hex[val >> 4];
+ *buf++ = hex[val & 0xf];
+ }
+
+ return base;
+}
+
+char *sha1_pack_index_name(const unsigned char *sha1)
+{
+ static const char hex[] = "0123456789abcdef";
+ static char *name, *base, *buf;
+ int i;
+
+ if (!base) {
+ const char *sha1_file_directory = get_object_directory();
+ int len = strlen(sha1_file_directory);
+ base = xmalloc(len + 60);
+ sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory);
+ name = base + len + 11;
+ }
+
+ buf = name;
+
+ for (i = 0; i < 20; i++) {
+ unsigned int val = *sha1++;
+ *buf++ = hex[val >> 4];
+ *buf++ = hex[val & 0xf];
+ }
+
+ return base;
+}
+
+struct alternate_object_database *alt_odb_list;
+static struct alternate_object_database **alt_odb_tail;
+
+/*
+ * Prepare alternate object database registry.
+ *
+ * The variable alt_odb_list points at the list of struct
+ * alternate_object_database. The elements on this list come from
+ * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT
+ * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates,
+ * whose contents is exactly in the same format as that environment
+ * variable. Its base points at a statically allocated buffer that
+ * contains "/the/directory/corresponding/to/.git/objects/...", while
+ * its name points just after the slash at the end of ".git/objects/"
+ * in the example above, and has enough space to hold 40-byte hex
+ * SHA1, an extra slash for the first level indirection, and the
+ * terminating NUL.
+ */
+static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
+ const char *relative_base)
+{
+ const char *cp, *last;
+ struct alternate_object_database *ent;
+ int base_len = -1;
+
+ last = alt;
+ while (last < ep) {
+ cp = last;
+ if (cp < ep && *cp == '#') {
+ while (cp < ep && *cp != sep)
+ cp++;
+ last = cp + 1;
+ continue;
+ }
+ for ( ; cp < ep && *cp != sep; cp++)
+ ;
+ if (last != cp) {
+ /* 43 = 40-byte + 2 '/' + terminating NUL */
+ int pfxlen = cp - last;
+ int entlen = pfxlen + 43;
+
+ if (*last != '/' && relative_base) {
+ /* Relative alt-odb */
+ if (base_len < 0)
+ base_len = strlen(relative_base) + 1;
+ entlen += base_len;
+ pfxlen += base_len;
+ }
+ ent = xmalloc(sizeof(*ent) + entlen);
+ *alt_odb_tail = ent;
+ alt_odb_tail = &(ent->next);
+ ent->next = NULL;
+ if (*last != '/' && relative_base) {
+ memcpy(ent->base, relative_base, base_len - 1);
+ ent->base[base_len - 1] = '/';
+ memcpy(ent->base + base_len,
+ last, cp - last);
+ }
+ else
+ memcpy(ent->base, last, pfxlen);
+ ent->name = ent->base + pfxlen + 1;
+ ent->base[pfxlen] = ent->base[pfxlen + 3] = '/';
+ ent->base[entlen-1] = 0;
+ }
+ while (cp < ep && *cp == sep)
+ cp++;
+ last = cp;
+ }
+}
+
+void prepare_alt_odb(void)
+{
+ char path[PATH_MAX];
+ char *map;
+ int fd;
+ struct stat st;
+ char *alt;
+
+ alt = getenv(ALTERNATE_DB_ENVIRONMENT);
+ if (!alt) alt = "";
+
+ if (alt_odb_tail)
+ return;
+ alt_odb_tail = &alt_odb_list;
+ link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL);
+
+ sprintf(path, "%s/info/alternates", get_object_directory());
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return;
+ if (fstat(fd, &st) || (st.st_size == 0)) {
+ close(fd);
+ return;
+ }
+ map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (map == MAP_FAILED)
+ return;
+
+ link_alt_odb_entries(map, map + st.st_size, '\n',
+ get_object_directory());
+ munmap(map, st.st_size);
+}
+
+static char *find_sha1_file(const unsigned char *sha1, struct stat *st)
+{
+ char *name = sha1_file_name(sha1);
+ struct alternate_object_database *alt;
+
+ if (!stat(name, st))
+ return name;
+ prepare_alt_odb();
+ for (alt = alt_odb_list; alt; alt = alt->next) {
+ name = alt->name;
+ fill_sha1_path(name, sha1);
+ if (!stat(alt->base, st))
+ return alt->base;
+ }
+ return NULL;
+}
+
+#define PACK_MAX_SZ (1<<26)
+static int pack_used_ctr;
+static unsigned long pack_mapped;
+struct packed_git *packed_git;
+
+static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
+ void **idx_map_)
+{
+ void *idx_map;
+ unsigned int *index;
+ unsigned long idx_size;
+ int nr, i;
+ int fd = open(path, O_RDONLY);
+ struct stat st;
+ if (fd < 0)
+ return -1;
+ if (fstat(fd, &st)) {
+ close(fd);
+ return -1;
+ }
+ idx_size = st.st_size;
+ idx_map = mmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (idx_map == MAP_FAILED)
+ return -1;
+
+ index = idx_map;
+ *idx_map_ = idx_map;
+ *idx_size_ = idx_size;
+
+ /* check index map */
+ if (idx_size < 4*256 + 20 + 20)
+ return error("index file too small");
+ nr = 0;
+ for (i = 0; i < 256; i++) {
+ unsigned int n = ntohl(index[i]);
+ if (n < nr)
+ return error("non-monotonic index");
+ nr = n;
+ }
+
+ /*
+ * Total size:
+ * - 256 index entries 4 bytes each
+ * - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
+ * - 20-byte SHA1 of the packfile
+ * - 20-byte SHA1 file checksum
+ */
+ if (idx_size != 4*256 + nr * 24 + 20 + 20)
+ return error("wrong index file size");
+
+ return 0;
+}
+
+static int unuse_one_packed_git(void)
+{
+ struct packed_git *p, *lru = NULL;
+
+ for (p = packed_git; p; p = p->next) {
+ if (p->pack_use_cnt || !p->pack_base)
+ continue;
+ if (!lru || p->pack_last_used < lru->pack_last_used)
+ lru = p;
+ }
+ if (!lru)
+ return 0;
+ munmap(lru->pack_base, lru->pack_size);
+ lru->pack_base = NULL;
+ return 1;
+}
+
+void unuse_packed_git(struct packed_git *p)
+{
+ p->pack_use_cnt--;
+}
+
+int use_packed_git(struct packed_git *p)
+{
+ if (!p->pack_size) {
+ struct stat st;
+ // We created the struct before we had the pack
+ stat(p->pack_name, &st);
+ if (!S_ISREG(st.st_mode))
+ die("packfile %s not a regular file", p->pack_name);
+ p->pack_size = st.st_size;
+ }
+ if (!p->pack_base) {
+ int fd;
+ struct stat st;
+ void *map;
+
+ pack_mapped += p->pack_size;
+ while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git())
+ ; /* nothing */
+ fd = open(p->pack_name, O_RDONLY);
+ if (fd < 0)
+ die("packfile %s cannot be opened", p->pack_name);
+ if (fstat(fd, &st)) {
+ close(fd);
+ die("packfile %s cannot be opened", p->pack_name);
+ }
+ if (st.st_size != p->pack_size)
+ die("packfile %s size mismatch.", p->pack_name);
+ map = mmap(NULL, p->pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (map == MAP_FAILED)
+ die("packfile %s cannot be mapped.", p->pack_name);
+ p->pack_base = map;
+
+ /* Check if the pack file matches with the index file.
+ * this is cheap.
+ */
+ if (memcmp((char*)(p->index_base) + p->index_size - 40,
+ p->pack_base + p->pack_size - 20, 20)) {
+
+ die("packfile %s does not match index.", p->pack_name);
+ }
+ }
+ p->pack_last_used = pack_used_ctr++;
+ p->pack_use_cnt++;
+ return 0;
+}
+
+struct packed_git *add_packed_git(char *path, int path_len, int local)
+{
+ struct stat st;
+ struct packed_git *p;
+ unsigned long idx_size;
+ void *idx_map;
+ unsigned char sha1[20];
+
+ if (check_packed_git_idx(path, &idx_size, &idx_map))
+ return NULL;
+
+ /* do we have a corresponding .pack file? */
+ strcpy(path + path_len - 4, ".pack");
+ if (stat(path, &st) || !S_ISREG(st.st_mode)) {
+ munmap(idx_map, idx_size);
+ return NULL;
+ }
+ /* ok, it looks sane as far as we can check without
+ * actually mapping the pack file.
+ */
+ p = xmalloc(sizeof(*p) + path_len + 2);
+ strcpy(p->pack_name, path);
+ p->index_size = idx_size;
+ p->pack_size = st.st_size;
+ p->index_base = idx_map;
+ p->next = NULL;
+ p->pack_base = NULL;
+ p->pack_last_used = 0;
+ p->pack_use_cnt = 0;
+ p->pack_local = local;
+ if (!get_sha1_hex(path + path_len - 40 - 4, sha1))
+ memcpy(p->sha1, sha1, 20);
+ return p;
+}
+
+struct packed_git *parse_pack_index(unsigned char *sha1)
+{
+ char *path = sha1_pack_index_name(sha1);
+ return parse_pack_index_file(sha1, path);
+}
+
+struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_path)
+{
+ struct packed_git *p;
+ unsigned long idx_size;
+ void *idx_map;
+ char *path;
+
+ if (check_packed_git_idx(idx_path, &idx_size, &idx_map))
+ return NULL;
+
+ path = sha1_pack_name(sha1);
+
+ p = xmalloc(sizeof(*p) + strlen(path) + 2);
+ strcpy(p->pack_name, path);
+ p->index_size = idx_size;
+ p->pack_size = 0;
+ p->index_base = idx_map;
+ p->next = NULL;
+ p->pack_base = NULL;
+ p->pack_last_used = 0;
+ p->pack_use_cnt = 0;
+ memcpy(p->sha1, sha1, 20);
+ return p;
+}
+
+void install_packed_git(struct packed_git *pack)
+{
+ pack->next = packed_git;
+ packed_git = pack;
+}
+
+static void prepare_packed_git_one(char *objdir, int local)
+{
+ char path[PATH_MAX];
+ int len;
+ DIR *dir;
+ struct dirent *de;
+
+ sprintf(path, "%s/pack", objdir);
+ len = strlen(path);
+ dir = opendir(path);
+ if (!dir)
+ return;
+ path[len++] = '/';
+ while ((de = readdir(dir)) != NULL) {
+ int namelen = strlen(de->d_name);
+ struct packed_git *p;
+
+ if (strcmp(de->d_name + namelen - 4, ".idx"))
+ continue;
+
+ /* we have .idx. Is it a file we can map? */
+ strcpy(path + len, de->d_name);
+ p = add_packed_git(path, len + namelen, local);
+ if (!p)
+ continue;
+ p->next = packed_git;
+ packed_git = p;
+ }
+ closedir(dir);
+}
+
+void prepare_packed_git(void)
+{
+ static int run_once = 0;
+ struct alternate_object_database *alt;
+
+ if (run_once)
+ return;
+ prepare_packed_git_one(get_object_directory(), 1);
+ prepare_alt_odb();
+ for (alt = alt_odb_list; alt; alt = alt->next) {
+ alt->name[0] = 0;
+ prepare_packed_git_one(alt->base, 0);
+ }
+ run_once = 1;
+}
+
+int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
+{
+ char header[100];
+ unsigned char real_sha1[20];
+ SHA_CTX c;
+
+ SHA1_Init(&c);
+ SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size));
+ SHA1_Update(&c, map, size);
+ SHA1_Final(real_sha1, &c);
+ return memcmp(sha1, real_sha1, 20) ? -1 : 0;
+}
+
+static void *map_sha1_file_internal(const unsigned char *sha1,
+ unsigned long *size)
+{
+ struct stat st;
+ void *map;
+ int fd;
+ char *filename = find_sha1_file(sha1, &st);
+
+ if (!filename) {
+ return NULL;
+ }
+
+ fd = open(filename, O_RDONLY | sha1_file_open_flag);
+ if (fd < 0) {
+ /* See if it works without O_NOATIME */
+ switch (sha1_file_open_flag) {
+ default:
+ fd = open(filename, O_RDONLY);
+ if (fd >= 0)
+ break;
+ /* Fallthrough */
+ case 0:
+ return NULL;
+ }
+
+ /* If it failed once, it will probably fail again.
+ * Stop using O_NOATIME
+ */
+ sha1_file_open_flag = 0;
+ }
+ map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (map == MAP_FAILED)
+ return NULL;
+ *size = st.st_size;
+ return map;
+}
+
+int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size)
+{
+ /* Get the data stream */
+ memset(stream, 0, sizeof(*stream));
+ stream->next_in = map;
+ stream->avail_in = mapsize;
+ stream->next_out = buffer;
+ stream->avail_out = size;
+
+ inflateInit(stream);
+ return inflate(stream, 0);
+}
+
+static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
+{
+ int bytes = strlen(buffer) + 1;
+ unsigned char *buf = xmalloc(1+size);
+
+ memcpy(buf, buffer + bytes, stream->total_out - bytes);
+ bytes = stream->total_out - bytes;
+ if (bytes < size) {
+ stream->next_out = buf + bytes;
+ stream->avail_out = size - bytes;
+ while (inflate(stream, Z_FINISH) == Z_OK)
+ /* nothing */;
+ }
+ buf[size] = 0;
+ inflateEnd(stream);
+ return buf;
+}
+
+/*
+ * We used to just use "sscanf()", but that's actually way
+ * too permissive for what we want to check. So do an anal
+ * object header parse by hand.
+ */
+int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
+{
+ int i;
+ unsigned long size;
+
+ /*
+ * The type can be at most ten bytes (including the
+ * terminating '\0' that we add), and is followed by
+ * a space.
+ */
+ i = 10;
+ for (;;) {
+ char c = *hdr++;
+ if (c == ' ')
+ break;
+ if (!--i)
+ return -1;
+ *type++ = c;
+ }
+ *type = 0;
+
+ /*
+ * The length must follow immediately, and be in canonical
+ * decimal format (ie "010" is not valid).
+ */
+ size = *hdr++ - '0';
+ if (size > 9)
+ return -1;
+ if (size) {
+ for (;;) {
+ unsigned long c = *hdr - '0';
+ if (c > 9)
+ break;
+ hdr++;
+ size = size * 10 + c;
+ }
+ }
+ *sizep = size;
+
+ /*
+ * The length must be followed by a zero byte
+ */
+ return *hdr ? -1 : 0;
+}
+
+void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size)
+{
+ int ret;
+ z_stream stream;
+ char hdr[8192];
+
+ ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
+ if (ret < Z_OK || parse_sha1_header(hdr, type, size) < 0)
+ return NULL;
+
+ return unpack_sha1_rest(&stream, hdr, *size);
+}
+
+/* forward declaration for a mutually recursive function */
+static int packed_object_info(struct pack_entry *entry,
+ char *type, unsigned long *sizep);
+
+static int packed_delta_info(unsigned char *base_sha1,
+ unsigned long delta_size,
+ unsigned long left,
+ char *type,
+ unsigned long *sizep,
+ struct packed_git *p)
+{
+ struct pack_entry base_ent;
+
+ if (left < 20)
+ die("truncated pack file");
+
+ /* The base entry _must_ be in the same pack */
+ if (!find_pack_entry_one(base_sha1, &base_ent, p))
+ die("failed to find delta-pack base object %s",
+ sha1_to_hex(base_sha1));
+
+ /* We choose to only get the type of the base object and
+ * ignore potentially corrupt pack file that expects the delta
+ * based on a base with a wrong size. This saves tons of
+ * inflate() calls.
+ */
+
+ if (packed_object_info(&base_ent, type, NULL))
+ die("cannot get info for delta-pack base");
+
+ if (sizep) {
+ const unsigned char *data;
+ unsigned char delta_head[64];
+ unsigned long result_size;
+ z_stream stream;
+ int st;
+
+ memset(&stream, 0, sizeof(stream));
+
+ data = stream.next_in = base_sha1 + 20;
+ stream.avail_in = left - 20;
+ stream.next_out = delta_head;
+ stream.avail_out = sizeof(delta_head);
+
+ inflateInit(&stream);
+ st = inflate(&stream, Z_FINISH);
+ inflateEnd(&stream);
+ if ((st != Z_STREAM_END) &&
+ stream.total_out != sizeof(delta_head))
+ die("delta data unpack-initial failed");
+
+ /* Examine the initial part of the delta to figure out
+ * the result size.
+ */
+ data = delta_head;
+ get_delta_hdr_size(&data); /* ignore base size */
+
+ /* Read the result size */
+ result_size = get_delta_hdr_size(&data);
+ *sizep = result_size;
+ }
+ return 0;
+}
+
+static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset,
+ enum object_type *type, unsigned long *sizep)
+{
+ unsigned shift;
+ unsigned char *pack, c;
+ unsigned long size;
+
+ if (offset >= p->pack_size)
+ die("object offset outside of pack file");
+
+ pack = p->pack_base + offset;
+ c = *pack++;
+ offset++;
+ *type = (c >> 4) & 7;
+ size = c & 15;
+ shift = 4;
+ while (c & 0x80) {
+ if (offset >= p->pack_size)
+ die("object offset outside of pack file");
+ c = *pack++;
+ offset++;
+ size += (c & 0x7f) << shift;
+ shift += 7;
+ }
+ *sizep = size;
+ return offset;
+}
+
+void packed_object_info_detail(struct pack_entry *e,
+ char *type,
+ unsigned long *size,
+ unsigned long *store_size,
+ int *delta_chain_length,
+ unsigned char *base_sha1)
+{
+ struct packed_git *p = e->p;
+ unsigned long offset, left;
+ unsigned char *pack;
+ enum object_type kind;
+
+ offset = unpack_object_header(p, e->offset, &kind, size);
+ pack = p->pack_base + offset;
+ left = p->pack_size - offset;
+ if (kind != OBJ_DELTA)
+ *delta_chain_length = 0;
+ else {
+ int chain_length = 0;
+ memcpy(base_sha1, pack, 20);
+ do {
+ struct pack_entry base_ent;
+ unsigned long junk;
+
+ find_pack_entry_one(pack, &base_ent, p);
+ offset = unpack_object_header(p, base_ent.offset,
+ &kind, &junk);
+ pack = p->pack_base + offset;
+ chain_length++;
+ } while (kind == OBJ_DELTA);
+ *delta_chain_length = chain_length;
+ }
+ switch (kind) {
+ case OBJ_COMMIT:
+ strcpy(type, "commit");
+ break;
+ case OBJ_TREE:
+ strcpy(type, "tree");
+ break;
+ case OBJ_BLOB:
+ strcpy(type, "blob");
+ break;
+ case OBJ_TAG:
+ strcpy(type, "tag");
+ break;
+ default:
+ die("corrupted pack file %s containing object of kind %d",
+ p->pack_name, kind);
+ }
+ *store_size = 0; /* notyet */
+}
+
+static int packed_object_info(struct pack_entry *entry,
+ char *type, unsigned long *sizep)
+{
+ struct packed_git *p = entry->p;
+ unsigned long offset, size, left;
+ unsigned char *pack;
+ enum object_type kind;
+ int retval;
+
+ if (use_packed_git(p))
+ die("cannot map packed file");
+
+ offset = unpack_object_header(p, entry->offset, &kind, &size);
+ pack = p->pack_base + offset;
+ left = p->pack_size - offset;
+
+ switch (kind) {
+ case OBJ_DELTA:
+ retval = packed_delta_info(pack, size, left, type, sizep, p);
+ unuse_packed_git(p);
+ return retval;
+ case OBJ_COMMIT:
+ strcpy(type, "commit");
+ break;
+ case OBJ_TREE:
+ strcpy(type, "tree");
+ break;
+ case OBJ_BLOB:
+ strcpy(type, "blob");
+ break;
+ case OBJ_TAG:
+ strcpy(type, "tag");
+ break;
+ default:
+ die("corrupted pack file %s containing object of kind %d",
+ p->pack_name, kind);
+ }
+ if (sizep)
+ *sizep = size;
+ unuse_packed_git(p);
+ return 0;
+}
+
+/* forward declaration for a mutually recursive function */
+static void *unpack_entry(struct pack_entry *, char *, unsigned long *);
+
+static void *unpack_delta_entry(unsigned char *base_sha1,
+ unsigned long delta_size,
+ unsigned long left,
+ char *type,
+ unsigned long *sizep,
+ struct packed_git *p)
+{
+ struct pack_entry base_ent;
+ void *data, *delta_data, *result, *base;
+ unsigned long data_size, result_size, base_size;
+ z_stream stream;
+ int st;
+
+ if (left < 20)
+ die("truncated pack file");
+ data = base_sha1 + 20;
+ data_size = left - 20;
+ delta_data = xmalloc(delta_size);
+
+ memset(&stream, 0, sizeof(stream));
+
+ stream.next_in = data;
+ stream.avail_in = data_size;
+ stream.next_out = delta_data;
+ stream.avail_out = delta_size;
+
+ inflateInit(&stream);
+ st = inflate(&stream, Z_FINISH);
+ inflateEnd(&stream);
+ if ((st != Z_STREAM_END) || stream.total_out != delta_size)
+ die("delta data unpack failed");
+
+ /* The base entry _must_ be in the same pack */
+ if (!find_pack_entry_one(base_sha1, &base_ent, p))
+ die("failed to find delta-pack base object %s",
+ sha1_to_hex(base_sha1));
+ base = unpack_entry_gently(&base_ent, type, &base_size);
+ if (!base)
+ die("failed to read delta-pack base object %s",
+ sha1_to_hex(base_sha1));
+ result = patch_delta(base, base_size,
+ delta_data, delta_size,
+ &result_size);
+ if (!result)
+ die("failed to apply delta");
+ free(delta_data);
+ free(base);
+ *sizep = result_size;
+ return result;
+}
+
+static void *unpack_non_delta_entry(unsigned char *data,
+ unsigned long size,
+ unsigned long left)
+{
+ int st;
+ z_stream stream;
+ unsigned char *buffer;
+
+ buffer = xmalloc(size + 1);
+ buffer[size] = 0;
+ memset(&stream, 0, sizeof(stream));
+ stream.next_in = data;
+ stream.avail_in = left;
+ stream.next_out = buffer;
+ stream.avail_out = size;
+
+ inflateInit(&stream);
+ st = inflate(&stream, Z_FINISH);
+ inflateEnd(&stream);
+ if ((st != Z_STREAM_END) || stream.total_out != size) {
+ free(buffer);
+ return NULL;
+ }
+
+ return buffer;
+}
+
+static void *unpack_entry(struct pack_entry *entry,
+ char *type, unsigned long *sizep)
+{
+ struct packed_git *p = entry->p;
+ void *retval;
+
+ if (use_packed_git(p))
+ die("cannot map packed file");
+ retval = unpack_entry_gently(entry, type, sizep);
+ unuse_packed_git(p);
+ if (!retval)
+ die("corrupted pack file %s", p->pack_name);
+ return retval;
+}
+
+/* The caller is responsible for use_packed_git()/unuse_packed_git() pair */
+void *unpack_entry_gently(struct pack_entry *entry,
+ char *type, unsigned long *sizep)
+{
+ struct packed_git *p = entry->p;
+ unsigned long offset, size, left;
+ unsigned char *pack;
+ enum object_type kind;
+ void *retval;
+
+ offset = unpack_object_header(p, entry->offset, &kind, &size);
+ pack = p->pack_base + offset;
+ left = p->pack_size - offset;
+ switch (kind) {
+ case OBJ_DELTA:
+ retval = unpack_delta_entry(pack, size, left, type, sizep, p);
+ return retval;
+ case OBJ_COMMIT:
+ strcpy(type, "commit");
+ break;
+ case OBJ_TREE:
+ strcpy(type, "tree");
+ break;
+ case OBJ_BLOB:
+ strcpy(type, "blob");
+ break;
+ case OBJ_TAG:
+ strcpy(type, "tag");
+ break;
+ default:
+ return NULL;
+ }
+ *sizep = size;
+ retval = unpack_non_delta_entry(pack, size, left);
+ return retval;
+}
+
+int num_packed_objects(const struct packed_git *p)
+{
+ /* See check_packed_git_idx() */
+ return (p->index_size - 20 - 20 - 4*256) / 24;
+}
+
+int nth_packed_object_sha1(const struct packed_git *p, int n,
+ unsigned char* sha1)
+{
+ void *index = p->index_base + 256;
+ if (n < 0 || num_packed_objects(p) <= n)
+ return -1;
+ memcpy(sha1, (index + 24 * n + 4), 20);
+ return 0;
+}
+
+int find_pack_entry_one(const unsigned char *sha1,
+ struct pack_entry *e, struct packed_git *p)
+{
+ unsigned int *level1_ofs = p->index_base;
+ int hi = ntohl(level1_ofs[*sha1]);
+ int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
+ void *index = p->index_base + 256;
+
+ do {
+ int mi = (lo + hi) / 2;
+ int cmp = memcmp(index + 24 * mi + 4, sha1, 20);
+ if (!cmp) {
+ e->offset = ntohl(*((int*)(index + 24 * mi)));
+ memcpy(e->sha1, sha1, 20);
+ e->p = p;
+ return 1;
+ }
+ if (cmp > 0)
+ hi = mi;
+ else
+ lo = mi+1;
+ } while (lo < hi);
+ return 0;
+}
+
+static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
+{
+ struct packed_git *p;
+ prepare_packed_git();
+
+ for (p = packed_git; p; p = p->next) {
+ if (find_pack_entry_one(sha1, e, p))
+ return 1;
+ }
+ return 0;
+}
+
+struct packed_git *find_sha1_pack(const unsigned char *sha1,
+ struct packed_git *packs)
+{
+ struct packed_git *p;
+ struct pack_entry e;
+
+ for (p = packs; p; p = p->next) {
+ if (find_pack_entry_one(sha1, &e, p))
+ return p;
+ }
+ return NULL;
+
+}
+
+int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+{
+ int status;
+ unsigned long mapsize, size;
+ void *map;
+ z_stream stream;
+ char hdr[128];
+
+ map = map_sha1_file_internal(sha1, &mapsize);
+ if (!map) {
+ struct pack_entry e;
+
+ if (!find_pack_entry(sha1, &e))
+ return error("unable to find %s", sha1_to_hex(sha1));
+ return packed_object_info(&e, type, sizep);
+ }
+ if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
+ status = error("unable to unpack %s header",
+ sha1_to_hex(sha1));
+ if (parse_sha1_header(hdr, type, &size) < 0)
+ status = error("unable to parse %s header", sha1_to_hex(sha1));
+ else {
+ status = 0;
+ if (sizep)
+ *sizep = size;
+ }
+ inflateEnd(&stream);
+ munmap(map, mapsize);
+ return status;
+}
+
+static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size)
+{
+ struct pack_entry e;
+
+ if (!find_pack_entry(sha1, &e)) {
+ error("cannot read sha1_file for %s", sha1_to_hex(sha1));
+ return NULL;
+ }
+ return unpack_entry(&e, type, size);
+}
+
+void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size)
+{
+ unsigned long mapsize;
+ void *map, *buf;
+ struct pack_entry e;
+
+ if (find_pack_entry(sha1, &e))
+ return read_packed_sha1(sha1, type, size);
+ map = map_sha1_file_internal(sha1, &mapsize);
+ if (map) {
+ buf = unpack_sha1_file(map, mapsize, type, size);
+ munmap(map, mapsize);
+ return buf;
+ }
+ return NULL;
+}
+
+void *read_object_with_reference(const unsigned char *sha1,
+ const char *required_type,
+ unsigned long *size,
+ unsigned char *actual_sha1_return)
+{
+ char type[20];
+ void *buffer;
+ unsigned long isize;
+ unsigned char actual_sha1[20];
+
+ memcpy(actual_sha1, sha1, 20);
+ while (1) {
+ int ref_length = -1;
+ const char *ref_type = NULL;
+
+ buffer = read_sha1_file(actual_sha1, type, &isize);
+ if (!buffer)
+ return NULL;
+ if (!strcmp(type, required_type)) {
+ *size = isize;
+ if (actual_sha1_return)
+ memcpy(actual_sha1_return, actual_sha1, 20);
+ return buffer;
+ }
+ /* Handle references */
+ else if (!strcmp(type, "commit"))
+ ref_type = "tree ";
+ else if (!strcmp(type, "tag"))
+ ref_type = "object ";
+ else {
+ free(buffer);
+ return NULL;
+ }
+ ref_length = strlen(ref_type);
+
+ if (memcmp(buffer, ref_type, ref_length) ||
+ get_sha1_hex(buffer + ref_length, actual_sha1)) {
+ free(buffer);
+ return NULL;
+ }
+ free(buffer);
+ /* Now we have the ID of the referred-to object in
+ * actual_sha1. Check again. */
+ }
+}
+
+char *write_sha1_file_prepare(void *buf,
+ unsigned long len,
+ const char *type,
+ unsigned char *sha1,
+ unsigned char *hdr,
+ int *hdrlen)
+{
+ SHA_CTX c;
+
+ /* Generate the header */
+ *hdrlen = sprintf((char *)hdr, "%s %lu", type, len)+1;
+
+ /* Sha1.. */
+ SHA1_Init(&c);
+ SHA1_Update(&c, hdr, *hdrlen);
+ SHA1_Update(&c, buf, len);
+ SHA1_Final(sha1, &c);
+
+ return sha1_file_name(sha1);
+}
+
+/*
+ * Link the tempfile to the final place, possibly creating the
+ * last directory level as you do so.
+ *
+ * Returns the errno on failure, 0 on success.
+ */
+static int link_temp_to_file(const char *tmpfile, char *filename)
+{
+ int ret;
+
+ if (!link(tmpfile, filename))
+ return 0;
+
+ /*
+ * Try to mkdir the last path component if that failed
+ * with an ENOENT.
+ *
+ * Re-try the "link()" regardless of whether the mkdir
+ * succeeds, since a race might mean that somebody
+ * else succeeded.
+ */
+ ret = errno;
+ if (ret == ENOENT) {
+ char *dir = strrchr(filename, '/');
+ if (dir) {
+ *dir = 0;
+ mkdir(filename, 0777);
+ *dir = '/';
+ if (!link(tmpfile, filename))
+ return 0;
+ ret = errno;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Move the just written object into its final resting place
+ */
+int move_temp_to_file(const char *tmpfile, char *filename)
+{
+ int ret = link_temp_to_file(tmpfile, filename);
+
+ /*
+ * Coda hack - coda doesn't like cross-directory links,
+ * so we fall back to a rename, which will mean that it
+ * won't be able to check collisions, but that's not a
+ * big deal.
+ *
+ * The same holds for FAT formatted media.
+ *
+ * When this succeeds, we just return 0. We have nothing
+ * left to unlink.
+ */
+ if (ret && ret != EEXIST) {
+ if (!rename(tmpfile, filename))
+ return 0;
+ ret = errno;
+ }
+ unlink(tmpfile);
+ if (ret) {
+ if (ret != EEXIST) {
+ fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret));
+ return -1;
+ }
+ /* FIXME!!! Collision check here ? */
+ }
+
+ return 0;
+}
+
+int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+{
+ int size;
+ unsigned char *compressed;
+ z_stream stream;
+ unsigned char sha1[20];
+ char *filename;
+ static char tmpfile[PATH_MAX];
+ unsigned char hdr[50];
+ int fd, hdrlen;
+
+ /* Normally if we have it in the pack then we do not bother writing
+ * it out into .git/objects/??/?{38} file.
+ */
+ filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+ if (returnsha1)
+ memcpy(returnsha1, sha1, 20);
+ if (has_sha1_file(sha1))
+ return 0;
+ fd = open(filename, O_RDONLY);
+ if (fd >= 0) {
+ /*
+ * FIXME!!! We might do collision checking here, but we'd
+ * need to uncompress the old file and check it. Later.
+ */
+ close(fd);
+ return 0;
+ }
+
+ if (errno != ENOENT) {
+ fprintf(stderr, "sha1 file %s: %s", filename, strerror(errno));
+ return -1;
+ }
+
+ snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+
+ fd = mkstemp(tmpfile);
+ if (fd < 0) {
+ fprintf(stderr, "unable to create temporary sha1 filename %s: %s", tmpfile, strerror(errno));
+ return -1;
+ }
+
+ /* Set it up */
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, Z_BEST_COMPRESSION);
+ size = deflateBound(&stream, len+hdrlen);
+ compressed = xmalloc(size);
+
+ /* Compress it */
+ stream.next_out = compressed;
+ stream.avail_out = size;
+
+ /* First header.. */
+ stream.next_in = hdr;
+ stream.avail_in = hdrlen;
+ while (deflate(&stream, 0) == Z_OK)
+ /* nothing */;
+
+ /* Then the data itself.. */
+ stream.next_in = buf;
+ stream.avail_in = len;
+ while (deflate(&stream, Z_FINISH) == Z_OK)
+ /* nothing */;
+ deflateEnd(&stream);
+ size = stream.total_out;
+
+ if (write(fd, compressed, size) != size)
+ die("unable to write file");
+ fchmod(fd, 0444);
+ close(fd);
+ free(compressed);
+
+ return move_temp_to_file(tmpfile, filename);
+}
+
+int write_sha1_to_fd(int fd, const unsigned char *sha1)
+{
+ ssize_t size;
+ unsigned long objsize;
+ int posn = 0;
+ void *map = map_sha1_file_internal(sha1, &objsize);
+ void *buf = map;
+ void *temp_obj = NULL;
+ z_stream stream;
+
+ if (!buf) {
+ unsigned char *unpacked;
+ unsigned long len;
+ char type[20];
+ char hdr[50];
+ int hdrlen;
+ // need to unpack and recompress it by itself
+ unpacked = read_packed_sha1(sha1, type, &len);
+
+ hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
+
+ /* Set it up */
+ memset(&stream, 0, sizeof(stream));
+ deflateInit(&stream, Z_BEST_COMPRESSION);
+ size = deflateBound(&stream, len + hdrlen);
+ temp_obj = buf = xmalloc(size);
+
+ /* Compress it */
+ stream.next_out = buf;
+ stream.avail_out = size;
+
+ /* First header.. */
+ stream.next_in = (void *)hdr;
+ stream.avail_in = hdrlen;
+ while (deflate(&stream, 0) == Z_OK)
+ /* nothing */;
+
+ /* Then the data itself.. */
+ stream.next_in = unpacked;
+ stream.avail_in = len;
+ while (deflate(&stream, Z_FINISH) == Z_OK)
+ /* nothing */;
+ deflateEnd(&stream);
+ free(unpacked);
+
+ objsize = stream.total_out;
+ }
+
+ do {
+ size = write(fd, buf + posn, objsize - posn);
+ if (size <= 0) {
+ if (!size) {
+ fprintf(stderr, "write closed");
+ } else {
+ perror("write ");
+ }
+ return -1;
+ }
+ posn += size;
+ } while (posn < objsize);
+
+ if (map)
+ munmap(map, objsize);
+ if (temp_obj)
+ free(temp_obj);
+
+ return 0;
+}
+
+int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
+ size_t bufsize, size_t *bufposn)
+{
+ char tmpfile[PATH_MAX];
+ int local;
+ z_stream stream;
+ unsigned char real_sha1[20];
+ unsigned char discard[4096];
+ int ret;
+ SHA_CTX c;
+
+ snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+
+ local = mkstemp(tmpfile);
+ if (local < 0)
+ return error("Couldn't open %s for %s\n", tmpfile, sha1_to_hex(sha1));
+
+ memset(&stream, 0, sizeof(stream));
+
+ inflateInit(&stream);
+
+ SHA1_Init(&c);
+
+ do {
+ ssize_t size;
+ if (*bufposn) {
+ stream.avail_in = *bufposn;
+ stream.next_in = (unsigned char *) buffer;
+ do {
+ stream.next_out = discard;
+ stream.avail_out = sizeof(discard);
+ ret = inflate(&stream, Z_SYNC_FLUSH);
+ SHA1_Update(&c, discard, sizeof(discard) -
+ stream.avail_out);
+ } while (stream.avail_in && ret == Z_OK);
+ write(local, buffer, *bufposn - stream.avail_in);
+ memmove(buffer, buffer + *bufposn - stream.avail_in,
+ stream.avail_in);
+ *bufposn = stream.avail_in;
+ if (ret != Z_OK)
+ break;
+ }
+ size = read(fd, buffer + *bufposn, bufsize - *bufposn);
+ if (size <= 0) {
+ close(local);
+ unlink(tmpfile);
+ if (!size)
+ return error("Connection closed?");
+ perror("Reading from connection");
+ return -1;
+ }
+ *bufposn += size;
+ } while (1);
+ inflateEnd(&stream);
+
+ close(local);
+ SHA1_Final(real_sha1, &c);
+ if (ret != Z_STREAM_END) {
+ unlink(tmpfile);
+ return error("File %s corrupted", sha1_to_hex(sha1));
+ }
+ if (memcmp(sha1, real_sha1, 20)) {
+ unlink(tmpfile);
+ return error("File %s has bad hash\n", sha1_to_hex(sha1));
+ }
+
+ return move_temp_to_file(tmpfile, sha1_file_name(sha1));
+}
+
+int has_pack_index(const unsigned char *sha1)
+{
+ struct stat st;
+ if (stat(sha1_pack_index_name(sha1), &st))
+ return 0;
+ return 1;
+}
+
+int has_pack_file(const unsigned char *sha1)
+{
+ struct stat st;
+ if (stat(sha1_pack_name(sha1), &st))
+ return 0;
+ return 1;
+}
+
+int has_sha1_pack(const unsigned char *sha1)
+{
+ struct pack_entry e;
+ return find_pack_entry(sha1, &e);
+}
+
+int has_sha1_file(const unsigned char *sha1)
+{
+ struct stat st;
+ struct pack_entry e;
+
+ if (find_pack_entry(sha1, &e))
+ return 1;
+ return find_sha1_file(sha1, &st) ? 1 : 0;
+}
+
+int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, const char *type)
+{
+ unsigned long size = st->st_size;
+ void *buf;
+ int ret;
+ unsigned char hdr[50];
+ int hdrlen;
+
+ buf = "";
+ if (size)
+ buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
+ if (buf == MAP_FAILED)
+ return -1;
+
+ if (!type)
+ type = "blob";
+ if (write_object)
+ ret = write_sha1_file(buf, size, type, sha1);
+ else {
+ write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
+ ret = 0;
+ }
+ if (size)
+ munmap(buf, size);
+ return ret;
+}
+
+int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object)
+{
+ int fd;
+ char *target;
+
+ switch (st->st_mode & S_IFMT) {
+ case S_IFREG:
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return error("open(\"%s\"): %s", path,
+ strerror(errno));
+ if (index_fd(sha1, fd, st, write_object, NULL) < 0)
+ return error("%s: failed to insert into database",
+ path);
+ break;
+ case S_IFLNK:
+ target = xmalloc(st->st_size+1);
+ if (readlink(path, target, st->st_size+1) != st->st_size) {
+ char *errstr = strerror(errno);
+ free(target);
+ return error("readlink(\"%s\"): %s", path,
+ errstr);
+ }
+ if (!write_object) {
+ unsigned char hdr[50];
+ int hdrlen;
+ write_sha1_file_prepare(target, st->st_size, "blob",
+ sha1, hdr, &hdrlen);
+ } else if (write_sha1_file(target, st->st_size, "blob", sha1))
+ return error("%s: failed to insert into database",
+ path);
+ free(target);
+ break;
+ default:
+ return error("%s: unsupported file type", path);
+ }
+ return 0;
+}
diff --git a/sha1_name.c b/sha1_name.c
new file mode 100644
index 000000000..be1755a70
--- /dev/null
+++ b/sha1_name.c
@@ -0,0 +1,444 @@
+#include "cache.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+
+static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
+{
+ struct alternate_object_database *alt;
+ char hex[40];
+ int found = 0;
+ static struct alternate_object_database *fakeent;
+
+ if (!fakeent) {
+ const char *objdir = get_object_directory();
+ int objdir_len = strlen(objdir);
+ int entlen = objdir_len + 43;
+ fakeent = xmalloc(sizeof(*fakeent) + entlen);
+ memcpy(fakeent->base, objdir, objdir_len);
+ fakeent->name = fakeent->base + objdir_len + 1;
+ fakeent->name[-1] = '/';
+ }
+ fakeent->next = alt_odb_list;
+
+ sprintf(hex, "%.2s", name);
+ for (alt = fakeent; alt && found < 2; alt = alt->next) {
+ struct dirent *de;
+ DIR *dir;
+ sprintf(alt->name, "%.2s/", name);
+ dir = opendir(alt->base);
+ if (!dir)
+ continue;
+ while ((de = readdir(dir)) != NULL) {
+ if (strlen(de->d_name) != 38)
+ continue;
+ if (memcmp(de->d_name, name + 2, len - 2))
+ continue;
+ if (!found) {
+ memcpy(hex + 2, de->d_name, 38);
+ found++;
+ }
+ else if (memcmp(hex + 2, de->d_name, 38)) {
+ found = 2;
+ break;
+ }
+ }
+ closedir(dir);
+ }
+ if (found == 1)
+ return get_sha1_hex(hex, sha1) == 0;
+ return found;
+}
+
+static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
+{
+ do {
+ if (*a != *b)
+ return 0;
+ a++;
+ b++;
+ len -= 2;
+ } while (len > 1);
+ if (len)
+ if ((*a ^ *b) & 0xf0)
+ return 0;
+ return 1;
+}
+
+static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
+{
+ struct packed_git *p;
+ unsigned char found_sha1[20];
+ int found = 0;
+
+ prepare_packed_git();
+ for (p = packed_git; p && found < 2; p = p->next) {
+ unsigned num = num_packed_objects(p);
+ unsigned first = 0, last = num;
+ while (first < last) {
+ unsigned mid = (first + last) / 2;
+ unsigned char now[20];
+ int cmp;
+
+ nth_packed_object_sha1(p, mid, now);
+ cmp = memcmp(match, now, 20);
+ if (!cmp) {
+ first = mid;
+ break;
+ }
+ if (cmp > 0) {
+ first = mid+1;
+ continue;
+ }
+ last = mid;
+ }
+ if (first < num) {
+ unsigned char now[20], next[20];
+ nth_packed_object_sha1(p, first, now);
+ if (match_sha(len, match, now)) {
+ if (nth_packed_object_sha1(p, first+1, next) ||
+ !match_sha(len, match, next)) {
+ /* unique within this pack */
+ if (!found) {
+ memcpy(found_sha1, now, 20);
+ found++;
+ }
+ else if (memcmp(found_sha1, now, 20)) {
+ found = 2;
+ break;
+ }
+ }
+ else {
+ /* not even unique within this pack */
+ found = 2;
+ break;
+ }
+ }
+ }
+ }
+ if (found == 1)
+ memcpy(sha1, found_sha1, 20);
+ return found;
+}
+
+#define SHORT_NAME_NOT_FOUND (-1)
+#define SHORT_NAME_AMBIGUOUS (-2)
+
+static int find_unique_short_object(int len, char *canonical,
+ unsigned char *res, unsigned char *sha1)
+{
+ int has_unpacked, has_packed;
+ unsigned char unpacked_sha1[20], packed_sha1[20];
+
+ has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1);
+ has_packed = find_short_packed_object(len, res, packed_sha1);
+ if (!has_unpacked && !has_packed)
+ return SHORT_NAME_NOT_FOUND;
+ if (1 < has_unpacked || 1 < has_packed)
+ return SHORT_NAME_AMBIGUOUS;
+ if (has_unpacked != has_packed) {
+ memcpy(sha1, (has_packed ? packed_sha1 : unpacked_sha1), 20);
+ return 0;
+ }
+ /* Both have unique ones -- do they match? */
+ if (memcmp(packed_sha1, unpacked_sha1, 20))
+ return -2;
+ memcpy(sha1, packed_sha1, 20);
+ return 0;
+}
+
+static int get_short_sha1(const char *name, int len, unsigned char *sha1,
+ int quietly)
+{
+ int i, status;
+ char canonical[40];
+ unsigned char res[20];
+
+ if (len < 4)
+ return -1;
+ memset(res, 0, 20);
+ memset(canonical, 'x', 40);
+ for (i = 0; i < len ;i++) {
+ unsigned char c = name[i];
+ unsigned char val;
+ if (c >= '0' && c <= '9')
+ val = c - '0';
+ else if (c >= 'a' && c <= 'f')
+ val = c - 'a' + 10;
+ else if (c >= 'A' && c <='F') {
+ val = c - 'A' + 10;
+ c -= 'A' - 'a';
+ }
+ else
+ return -1;
+ canonical[i] = c;
+ if (!(i & 1))
+ val <<= 4;
+ res[i >> 1] |= val;
+ }
+
+ status = find_unique_short_object(i, canonical, res, sha1);
+ if (!quietly && (status == SHORT_NAME_AMBIGUOUS))
+ return error("short SHA1 %.*s is ambiguous.", len, canonical);
+ return status;
+}
+
+const char *find_unique_abbrev(const unsigned char *sha1, int len)
+{
+ int status;
+ static char hex[41];
+ memcpy(hex, sha1_to_hex(sha1), 40);
+ while (len < 40) {
+ unsigned char sha1_ret[20];
+ status = get_short_sha1(hex, len, sha1_ret, 1);
+ if (!status) {
+ hex[len] = 0;
+ return hex;
+ }
+ if (status != SHORT_NAME_AMBIGUOUS)
+ return NULL;
+ len++;
+ }
+ return NULL;
+}
+
+static int ambiguous_path(const char *path)
+{
+ int slash = 1;
+
+ for (;;) {
+ switch (*path++) {
+ case '\0':
+ break;
+ case '/':
+ if (slash)
+ break;
+ slash = 1;
+ continue;
+ case '.':
+ continue;
+ default:
+ slash = 0;
+ continue;
+ }
+ return slash;
+ }
+}
+
+static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
+{
+ static const char *prefix[] = {
+ "",
+ "refs",
+ "refs/tags",
+ "refs/heads",
+ NULL
+ };
+ const char **p;
+
+ if (len == 40 && !get_sha1_hex(str, sha1))
+ return 0;
+
+ /* Accept only unambiguous ref paths. */
+ if (ambiguous_path(str))
+ return -1;
+
+ for (p = prefix; *p; p++) {
+ char *pathname = git_path("%s/%.*s", *p, len, str);
+ if (!read_ref(pathname, sha1))
+ return 0;
+ }
+
+ return -1;
+}
+
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
+static int get_parent(const char *name, int len,
+ unsigned char *result, int idx)
+{
+ unsigned char sha1[20];
+ int ret = get_sha1_1(name, len, sha1);
+ struct commit *commit;
+ struct commit_list *p;
+
+ if (ret)
+ return ret;
+ commit = lookup_commit_reference(sha1);
+ if (!commit)
+ return -1;
+ if (parse_commit(commit))
+ return -1;
+ if (!idx) {
+ memcpy(result, commit->object.sha1, 20);
+ return 0;
+ }
+ p = commit->parents;
+ while (p) {
+ if (!--idx) {
+ memcpy(result, p->item->object.sha1, 20);
+ return 0;
+ }
+ p = p->next;
+ }
+ return -1;
+}
+
+static int get_nth_ancestor(const char *name, int len,
+ unsigned char *result, int generation)
+{
+ unsigned char sha1[20];
+ int ret = get_sha1_1(name, len, sha1);
+ if (ret)
+ return ret;
+
+ while (generation--) {
+ struct commit *commit = lookup_commit_reference(sha1);
+
+ if (!commit || parse_commit(commit) || !commit->parents)
+ return -1;
+ memcpy(sha1, commit->parents->item->object.sha1, 20);
+ }
+ memcpy(result, sha1, 20);
+ return 0;
+}
+
+static int peel_onion(const char *name, int len, unsigned char *sha1)
+{
+ unsigned char outer[20];
+ const char *sp;
+ const char *type_string = NULL;
+ struct object *o;
+
+ /*
+ * "ref^{type}" dereferences ref repeatedly until you cannot
+ * dereference anymore, or you get an object of given type,
+ * whichever comes first. "ref^{}" means just dereference
+ * tags until you get a non-tag. "ref^0" is a shorthand for
+ * "ref^{commit}". "commit^{tree}" could be used to find the
+ * top-level tree of the given commit.
+ */
+ if (len < 4 || name[len-1] != '}')
+ return -1;
+
+ for (sp = name + len - 1; name <= sp; sp--) {
+ int ch = *sp;
+ if (ch == '{' && name < sp && sp[-1] == '^')
+ break;
+ }
+ if (sp <= name)
+ return -1;
+
+ sp++; /* beginning of type name, or closing brace for empty */
+ if (!strncmp(commit_type, sp, 6) && sp[6] == '}')
+ type_string = commit_type;
+ else if (!strncmp(tree_type, sp, 4) && sp[4] == '}')
+ type_string = tree_type;
+ else if (!strncmp(blob_type, sp, 4) && sp[4] == '}')
+ type_string = blob_type;
+ else if (sp[0] == '}')
+ type_string = NULL;
+ else
+ return -1;
+
+ if (get_sha1_1(name, sp - name - 2, outer))
+ return -1;
+
+ o = parse_object(outer);
+ if (!o)
+ return -1;
+ if (!type_string) {
+ o = deref_tag(o, name, sp - name - 2);
+ if (!o || (!o->parsed && !parse_object(o->sha1)))
+ return -1;
+ memcpy(sha1, o->sha1, 20);
+ }
+ else {
+ /* At this point, the syntax look correct, so
+ * if we do not get the needed object, we should
+ * barf.
+ */
+
+ while (1) {
+ if (!o || (!o->parsed && !parse_object(o->sha1)))
+ return -1;
+ if (o->type == type_string) {
+ memcpy(sha1, o->sha1, 20);
+ return 0;
+ }
+ if (o->type == tag_type)
+ o = ((struct tag*) o)->tagged;
+ else if (o->type == commit_type)
+ o = &(((struct commit *) o)->tree->object);
+ else
+ return error("%.*s: expected %s type, but the object dereferences to %s type",
+ len, name, type_string,
+ o->type);
+ if (!o->parsed)
+ parse_object(o->sha1);
+ }
+ }
+ return 0;
+}
+
+static int get_sha1_1(const char *name, int len, unsigned char *sha1)
+{
+ int parent, ret;
+ const char *cp;
+
+ /* foo^[0-9] or foo^ (== foo^1); we do not do more than 9 parents. */
+ if (len > 2 && name[len-2] == '^' &&
+ name[len-1] >= '0' && name[len-1] <= '9') {
+ parent = name[len-1] - '0';
+ len -= 2;
+ }
+ else if (len > 1 && name[len-1] == '^') {
+ parent = 1;
+ len--;
+ } else
+ parent = -1;
+
+ if (parent >= 0)
+ return get_parent(name, len, sha1, parent);
+
+ /* "name~3" is "name^^^",
+ * "name~12" is "name^^^^^^^^^^^^", and
+ * "name~" and "name~0" are name -- not "name^0"!
+ */
+ parent = 0;
+ for (cp = name + len - 1; name <= cp; cp--) {
+ int ch = *cp;
+ if ('0' <= ch && ch <= '9')
+ continue;
+ if (ch != '~')
+ parent = -1;
+ break;
+ }
+ if (!parent && *cp == '~') {
+ int len1 = cp - name;
+ cp++;
+ while (cp < name + len)
+ parent = parent * 10 + *cp++ - '0';
+ return get_nth_ancestor(name, len1, sha1, parent);
+ }
+
+ ret = peel_onion(name, len, sha1);
+ if (!ret)
+ return 0;
+
+ ret = get_sha1_basic(name, len, sha1);
+ if (!ret)
+ return 0;
+ return get_short_sha1(name, len, sha1, 0);
+}
+
+/*
+ * This is like "get_sha1_basic()", except it allows "sha1 expressions",
+ * notably "xyz^" for "parent of xyz"
+ */
+int get_sha1(const char *name, unsigned char *sha1)
+{
+ prepare_alt_odb();
+ return get_sha1_1(name, strlen(name), sha1);
+}
diff --git a/shell.c b/shell.c
new file mode 100644
index 000000000..2c4789e94
--- /dev/null
+++ b/shell.c
@@ -0,0 +1,59 @@
+#include "cache.h"
+#include "quote.h"
+
+static int do_generic_cmd(const char *me, char *arg)
+{
+ const char *my_argv[4];
+
+ arg = sq_dequote(arg);
+ if (!arg)
+ die("bad argument");
+
+ my_argv[0] = me;
+ my_argv[1] = arg;
+ my_argv[2] = NULL;
+
+ return execvp(me, (char**) my_argv);
+}
+
+static struct commands {
+ const char *name;
+ int (*exec)(const char *me, char *arg);
+} cmd_list[] = {
+ { "git-receive-pack", do_generic_cmd },
+ { "git-upload-pack", do_generic_cmd },
+ { NULL },
+};
+
+int main(int argc, char **argv)
+{
+ char *prog;
+ struct commands *cmd;
+
+ /* We want to see "-c cmd args", and nothing else */
+ if (argc != 3 || strcmp(argv[1], "-c"))
+ die("What do you think I am? A shell?");
+
+ prog = argv[2];
+ argv += 2;
+ argc -= 2;
+ for (cmd = cmd_list ; cmd->name ; cmd++) {
+ int len = strlen(cmd->name);
+ char *arg;
+ if (strncmp(cmd->name, prog, len))
+ continue;
+ arg = NULL;
+ switch (prog[len]) {
+ case '\0':
+ arg = NULL;
+ break;
+ case ' ':
+ arg = prog + len + 1;
+ break;
+ default:
+ continue;
+ }
+ exit(cmd->exec(cmd->name, arg));
+ }
+ die("unrecognized command '%s'", prog);
+}
diff --git a/show-branch.c b/show-branch.c
new file mode 100644
index 000000000..631336cd9
--- /dev/null
+++ b/show-branch.c
@@ -0,0 +1,571 @@
+#include <stdlib.h>
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+
+static const char show_branch_usage[] =
+"git-show-branch [--all] [--heads] [--tags] [--more=count | --list | --independent | --merge-base ] [<refs>...]";
+
+#define UNINTERESTING 01
+
+#define REV_SHIFT 2
+#define MAX_REVS 29 /* should not exceed bits_per_int - REV_SHIFT */
+
+static struct commit *interesting(struct commit_list *list)
+{
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (commit->object.flags & UNINTERESTING)
+ continue;
+ return commit;
+ }
+ return NULL;
+}
+
+static struct commit *pop_one_commit(struct commit_list **list_p)
+{
+ struct commit *commit;
+ struct commit_list *list;
+ list = *list_p;
+ commit = list->item;
+ *list_p = list->next;
+ free(list);
+ return commit;
+}
+
+struct commit_name {
+ const char *head_name; /* which head's ancestor? */
+ int generation; /* how many parents away from head_name */
+};
+
+/* Name the commit as nth generation ancestor of head_name;
+ * we count only the first-parent relationship for naming purposes.
+ */
+static void name_commit(struct commit *commit, const char *head_name, int nth)
+{
+ struct commit_name *name;
+ if (!commit->object.util)
+ commit->object.util = xmalloc(sizeof(struct commit_name));
+ name = commit->object.util;
+ name->head_name = head_name;
+ name->generation = nth;
+}
+
+/* Parent is the first parent of the commit. We may name it
+ * as (n+1)th generation ancestor of the same head_name as
+ * commit is nth generation ancestore of, if that generation
+ * number is better than the name it already has.
+ */
+static void name_parent(struct commit *commit, struct commit *parent)
+{
+ struct commit_name *commit_name = commit->object.util;
+ struct commit_name *parent_name = parent->object.util;
+ if (!commit_name)
+ return;
+ if (!parent_name ||
+ commit_name->generation + 1 < parent_name->generation)
+ name_commit(parent, commit_name->head_name,
+ commit_name->generation + 1);
+}
+
+static int name_first_parent_chain(struct commit *c)
+{
+ int i = 0;
+ while (c) {
+ struct commit *p;
+ if (!c->object.util)
+ break;
+ if (!c->parents)
+ break;
+ p = c->parents->item;
+ if (!p->object.util) {
+ name_parent(c, p);
+ i++;
+ }
+ c = p;
+ }
+ return i;
+}
+
+static void name_commits(struct commit_list *list,
+ struct commit **rev,
+ char **ref_name,
+ int num_rev)
+{
+ struct commit_list *cl;
+ struct commit *c;
+ int i;
+
+ /* First give names to the given heads */
+ for (cl = list; cl; cl = cl->next) {
+ c = cl->item;
+ if (c->object.util)
+ continue;
+ for (i = 0; i < num_rev; i++) {
+ if (rev[i] == c) {
+ name_commit(c, ref_name[i], 0);
+ break;
+ }
+ }
+ }
+
+ /* Then commits on the first parent ancestry chain */
+ do {
+ i = 0;
+ for (cl = list; cl; cl = cl->next) {
+ i += name_first_parent_chain(cl->item);
+ }
+ } while (i);
+
+ /* Finally, any unnamed commits */
+ do {
+ i = 0;
+ for (cl = list; cl; cl = cl->next) {
+ struct commit_list *parents;
+ struct commit_name *n;
+ int nth;
+ c = cl->item;
+ if (!c->object.util)
+ continue;
+ n = c->object.util;
+ parents = c->parents;
+ nth = 0;
+ while (parents) {
+ struct commit *p = parents->item;
+ char newname[1000], *en;
+ parents = parents->next;
+ nth++;
+ if (p->object.util)
+ continue;
+ en = newname;
+ switch (n->generation) {
+ case 0:
+ en += sprintf(en, "%s", n->head_name);
+ break;
+ case 1:
+ en += sprintf(en, "%s^", n->head_name);
+ break;
+ default:
+ en += sprintf(en, "%s~%d",
+ n->head_name, n->generation);
+ break;
+ }
+ if (nth == 1)
+ en += sprintf(en, "^");
+ else
+ en += sprintf(en, "^%d", nth);
+ name_commit(p, strdup(newname), 0);
+ i++;
+ name_first_parent_chain(p);
+ }
+ }
+ } while (i);
+}
+
+static int mark_seen(struct commit *commit, struct commit_list **seen_p)
+{
+ if (!commit->object.flags) {
+ insert_by_date(commit, seen_p);
+ return 1;
+ }
+ return 0;
+}
+
+static void join_revs(struct commit_list **list_p,
+ struct commit_list **seen_p,
+ int num_rev, int extra)
+{
+ int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+ while (*list_p) {
+ struct commit_list *parents;
+ int still_interesting = !!interesting(*list_p);
+ struct commit *commit = pop_one_commit(list_p);
+ int flags = commit->object.flags & all_mask;
+
+ if (!still_interesting && extra <= 0)
+ break;
+
+ mark_seen(commit, seen_p);
+ if ((flags & all_revs) == all_revs)
+ flags |= UNINTERESTING;
+ parents = commit->parents;
+
+ while (parents) {
+ struct commit *p = parents->item;
+ int this_flag = p->object.flags;
+ parents = parents->next;
+ if ((this_flag & flags) == flags)
+ continue;
+ if (!p->object.parsed)
+ parse_commit(p);
+ if (mark_seen(p, seen_p) && !still_interesting)
+ extra--;
+ p->object.flags |= flags;
+ insert_by_date(p, list_p);
+ }
+ }
+
+ /*
+ * Postprocess to complete well-poisoning.
+ *
+ * At this point we have all the commits we have seen in
+ * seen_p list (which happens to be sorted chronologically but
+ * it does not really matter). Mark anything that can be
+ * reached from uninteresting commits not interesting.
+ */
+ for (;;) {
+ int changed = 0;
+ struct commit_list *s;
+ for (s = *seen_p; s; s = s->next) {
+ struct commit *c = s->item;
+ struct commit_list *parents;
+
+ if (((c->object.flags & all_revs) != all_revs) &&
+ !(c->object.flags & UNINTERESTING))
+ continue;
+
+ /* The current commit is either a merge base or
+ * already uninteresting one. Mark its parents
+ * as uninteresting commits _only_ if they are
+ * already parsed. No reason to find new ones
+ * here.
+ */
+ parents = c->parents;
+ while (parents) {
+ struct commit *p = parents->item;
+ parents = parents->next;
+ if (!(p->object.flags & UNINTERESTING)) {
+ p->object.flags |= UNINTERESTING;
+ changed = 1;
+ }
+ }
+ }
+ if (!changed)
+ break;
+ }
+}
+
+static void show_one_commit(struct commit *commit, int no_name)
+{
+ char pretty[256], *cp;
+ struct commit_name *name = commit->object.util;
+ if (commit->object.parsed)
+ pretty_print_commit(CMIT_FMT_ONELINE, commit->buffer, ~0,
+ pretty, sizeof(pretty));
+ else
+ strcpy(pretty, "(unavailable)");
+ if (!strncmp(pretty, "[PATCH] ", 8))
+ cp = pretty + 8;
+ else
+ cp = pretty;
+
+ if (!no_name) {
+ if (name && name->head_name) {
+ printf("[%s", name->head_name);
+ if (name->generation) {
+ if (name->generation == 1)
+ printf("^");
+ else
+ printf("~%d", name->generation);
+ }
+ printf("] ");
+ }
+ else
+ printf("[%s] ",
+ find_unique_abbrev(commit->object.sha1, 7));
+ }
+ puts(cp);
+}
+
+static char *ref_name[MAX_REVS + 1];
+static int ref_name_cnt;
+
+static int compare_ref_name(const void *a_, const void *b_)
+{
+ const char * const*a = a_, * const*b = b_;
+ return strcmp(*a, *b);
+}
+
+static void sort_ref_range(int bottom, int top)
+{
+ qsort(ref_name + bottom, top - bottom, sizeof(ref_name[0]),
+ compare_ref_name);
+}
+
+static int append_ref(const char *refname, const unsigned char *sha1)
+{
+ struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit)
+ return 0;
+ if (MAX_REVS <= ref_name_cnt) {
+ fprintf(stderr, "warning: ignoring %s; "
+ "cannot handle more than %d refs",
+ refname, MAX_REVS);
+ return 0;
+ }
+ ref_name[ref_name_cnt++] = strdup(refname);
+ ref_name[ref_name_cnt] = NULL;
+ return 0;
+}
+
+static int append_head_ref(const char *refname, const unsigned char *sha1)
+{
+ if (strncmp(refname, "refs/heads/", 11))
+ return 0;
+ return append_ref(refname + 11, sha1);
+}
+
+static int append_tag_ref(const char *refname, const unsigned char *sha1)
+{
+ if (strncmp(refname, "refs/tags/", 10))
+ return 0;
+ return append_ref(refname + 5, sha1);
+}
+
+static void snarf_refs(int head, int tag)
+{
+ if (head) {
+ int orig_cnt = ref_name_cnt;
+ for_each_ref(append_head_ref);
+ sort_ref_range(orig_cnt, ref_name_cnt);
+ }
+ if (tag) {
+ int orig_cnt = ref_name_cnt;
+ for_each_ref(append_tag_ref);
+ sort_ref_range(orig_cnt, ref_name_cnt);
+ }
+}
+
+static int rev_is_head(char *head_path, int headlen,
+ char *name,
+ unsigned char *head_sha1, unsigned char *sha1)
+{
+ int namelen;
+ if ((!head_path[0]) || memcmp(head_sha1, sha1, 20))
+ return 0;
+ namelen = strlen(name);
+ if ((headlen < namelen) ||
+ memcmp(head_path + headlen - namelen, name, namelen))
+ return 0;
+ if (headlen == namelen ||
+ head_path[headlen - namelen - 1] == '/')
+ return 1;
+ return 0;
+}
+
+static int show_merge_base(struct commit_list *seen, int num_rev)
+{
+ int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ int all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+ int exit_status = 1;
+
+ while (seen) {
+ struct commit *commit = pop_one_commit(&seen);
+ int flags = commit->object.flags & all_mask;
+ if (!(flags & UNINTERESTING) &&
+ ((flags & all_revs) == all_revs)) {
+ puts(sha1_to_hex(commit->object.sha1));
+ exit_status = 0;
+ commit->object.flags |= UNINTERESTING;
+ }
+ }
+ return exit_status;
+}
+
+static int show_independent(struct commit **rev,
+ int num_rev,
+ char **ref_name,
+ unsigned int *rev_mask)
+{
+ int i;
+
+ for (i = 0; i < num_rev; i++) {
+ struct commit *commit = rev[i];
+ unsigned int flag = rev_mask[i];
+
+ if (commit->object.flags == flag)
+ puts(sha1_to_hex(commit->object.sha1));
+ commit->object.flags |= UNINTERESTING;
+ }
+ return 0;
+}
+
+int main(int ac, char **av)
+{
+ struct commit *rev[MAX_REVS], *commit;
+ struct commit_list *list = NULL, *seen = NULL;
+ unsigned int rev_mask[MAX_REVS];
+ int num_rev, i, extra = 0;
+ int all_heads = 0, all_tags = 0;
+ int all_mask, all_revs;
+ char head_path[128];
+ const char *head_path_p;
+ int head_path_len;
+ unsigned char head_sha1[20];
+ int merge_base = 0;
+ int independent = 0;
+ int no_name = 0;
+ int sha1_name = 0;
+ int shown_merge_point = 0;
+ int topo_order = 0;
+
+ setup_git_directory();
+
+ while (1 < ac && av[1][0] == '-') {
+ char *arg = av[1];
+ if (!strcmp(arg, "--all"))
+ all_heads = all_tags = 1;
+ else if (!strcmp(arg, "--heads"))
+ all_heads = 1;
+ else if (!strcmp(arg, "--tags"))
+ all_tags = 1;
+ else if (!strcmp(arg, "--more"))
+ extra = 1;
+ else if (!strcmp(arg, "--list"))
+ extra = -1;
+ else if (!strcmp(arg, "--no-name"))
+ no_name = 1;
+ else if (!strcmp(arg, "--sha1-name"))
+ sha1_name = 1;
+ else if (!strncmp(arg, "--more=", 7))
+ extra = atoi(arg + 7);
+ else if (!strcmp(arg, "--merge-base"))
+ merge_base = 1;
+ else if (!strcmp(arg, "--independent"))
+ independent = 1;
+ else if (!strcmp(arg, "--topo-order"))
+ topo_order = 1;
+ else
+ usage(show_branch_usage);
+ ac--; av++;
+ }
+ ac--; av++;
+
+ /* Only one of these is allowed */
+ if (1 < independent + merge_base + (extra != 0))
+ usage(show_branch_usage);
+
+ if (all_heads + all_tags)
+ snarf_refs(all_heads, all_tags);
+
+ while (0 < ac) {
+ unsigned char revkey[20];
+ if (get_sha1(*av, revkey))
+ die("bad sha1 reference %s", *av);
+ append_ref(*av, revkey);
+ ac--; av++;
+ }
+
+ /* If still no revs, then add heads */
+ if (!ref_name_cnt)
+ snarf_refs(1, 0);
+
+ for (num_rev = 0; ref_name[num_rev]; num_rev++) {
+ unsigned char revkey[20];
+ unsigned int flag = 1u << (num_rev + REV_SHIFT);
+
+ if (MAX_REVS <= num_rev)
+ die("cannot handle more than %d revs.", MAX_REVS);
+ if (get_sha1(ref_name[num_rev], revkey))
+ usage(show_branch_usage);
+ commit = lookup_commit_reference(revkey);
+ if (!commit)
+ die("cannot find commit %s (%s)",
+ ref_name[num_rev], revkey);
+ parse_commit(commit);
+ mark_seen(commit, &seen);
+
+ /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
+ * and so on. REV_SHIFT bits from bit 0 are used for
+ * internal bookkeeping.
+ */
+ commit->object.flags |= flag;
+ if (commit->object.flags == flag)
+ insert_by_date(commit, &list);
+ rev[num_rev] = commit;
+ }
+ for (i = 0; i < num_rev; i++)
+ rev_mask[i] = rev[i]->object.flags;
+
+ if (0 <= extra)
+ join_revs(&list, &seen, num_rev, extra);
+
+ head_path_p = resolve_ref(git_path("HEAD"), head_sha1, 1);
+ if (head_path_p) {
+ head_path_len = strlen(head_path_p);
+ memcpy(head_path, head_path_p, head_path_len + 1);
+ }
+ else {
+ head_path_len = 0;
+ head_path[0] = 0;
+ }
+
+ if (merge_base)
+ return show_merge_base(seen, num_rev);
+
+ if (independent)
+ return show_independent(rev, num_rev, ref_name, rev_mask);
+
+ /* Show list; --more=-1 means list-only */
+ if (1 < num_rev || extra < 0) {
+ for (i = 0; i < num_rev; i++) {
+ int j;
+ int is_head = rev_is_head(head_path,
+ head_path_len,
+ ref_name[i],
+ head_sha1,
+ rev[i]->object.sha1);
+ if (extra < 0)
+ printf("%c [%s] ",
+ is_head ? '*' : ' ', ref_name[i]);
+ else {
+ for (j = 0; j < i; j++)
+ putchar(' ');
+ printf("%c [%s] ",
+ is_head ? '*' : '!', ref_name[i]);
+ }
+ /* header lines never need name */
+ show_one_commit(rev[i], 1);
+ }
+ if (0 <= extra) {
+ for (i = 0; i < num_rev; i++)
+ putchar('-');
+ putchar('\n');
+ }
+ }
+ if (extra < 0)
+ exit(0);
+
+ /* Sort topologically */
+ if (topo_order)
+ sort_in_topological_order(&seen);
+
+ /* Give names to commits */
+ if (!sha1_name && !no_name)
+ name_commits(seen, rev, ref_name, num_rev);
+
+ all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+ all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+
+ while (seen) {
+ struct commit *commit = pop_one_commit(&seen);
+ int this_flag = commit->object.flags;
+
+ shown_merge_point |= ((this_flag & all_revs) == all_revs);
+
+ if (1 < num_rev) {
+ for (i = 0; i < num_rev; i++)
+ putchar((this_flag & (1u << (i + REV_SHIFT)))
+ ? '+' : ' ');
+ putchar(' ');
+ }
+ show_one_commit(commit, no_name);
+
+ if (shown_merge_point && --extra < 0)
+ break;
+ }
+ return 0;
+}
diff --git a/show-index.c b/show-index.c
new file mode 100644
index 000000000..c21d660b6
--- /dev/null
+++ b/show-index.c
@@ -0,0 +1,28 @@
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+ int i;
+ unsigned nr;
+ unsigned int entry[6];
+ static unsigned int top_index[256];
+
+ if (fread(top_index, sizeof(top_index), 1, stdin) != 1)
+ die("unable to read idex");
+ nr = 0;
+ for (i = 0; i < 256; i++) {
+ unsigned n = ntohl(top_index[i]);
+ if (n < nr)
+ die("corrupt index file");
+ nr = n;
+ }
+ for (i = 0; i < nr; i++) {
+ unsigned offset;
+
+ if (fread(entry, 24, 1, stdin) != 1)
+ die("unable to read entry %u/%u", i, nr);
+ offset = ntohl(entry[0]);
+ printf("%u %s\n", offset, sha1_to_hex((void *)(entry+1)));
+ }
+ return 0;
+}
diff --git a/ssh-fetch.c b/ssh-fetch.c
new file mode 100644
index 000000000..bf01fbc00
--- /dev/null
+++ b/ssh-fetch.c
@@ -0,0 +1,170 @@
+#ifndef COUNTERPART_ENV_NAME
+#define COUNTERPART_ENV_NAME "GIT_SSH_UPLOAD"
+#endif
+#ifndef COUNTERPART_PROGRAM_NAME
+#define COUNTERPART_PROGRAM_NAME "git-ssh-upload"
+#endif
+#ifndef MY_PROGRAM_NAME
+#define MY_PROGRAM_NAME "git-ssh-fetch"
+#endif
+
+#include "cache.h"
+#include "commit.h"
+#include "rsh.h"
+#include "fetch.h"
+#include "refs.h"
+
+static int fd_in;
+static int fd_out;
+
+static unsigned char remote_version = 0;
+static unsigned char local_version = 1;
+
+static ssize_t force_write(int fd, void *buffer, size_t length)
+{
+ ssize_t ret = 0;
+ while (ret < length) {
+ ssize_t size = write(fd, buffer + ret, length - ret);
+ if (size < 0) {
+ return size;
+ }
+ if (size == 0) {
+ return ret;
+ }
+ ret += size;
+ }
+ return ret;
+}
+
+static int prefetches = 0;
+
+static struct object_list *in_transit = NULL;
+static struct object_list **end_of_transit = &in_transit;
+
+void prefetch(unsigned char *sha1)
+{
+ char type = 'o';
+ struct object_list *node;
+ if (prefetches > 100) {
+ fetch(in_transit->item->sha1);
+ }
+ node = xmalloc(sizeof(struct object_list));
+ node->next = NULL;
+ node->item = lookup_unknown_object(sha1);
+ *end_of_transit = node;
+ end_of_transit = &node->next;
+ force_write(fd_out, &type, 1);
+ force_write(fd_out, sha1, 20);
+ prefetches++;
+}
+
+static char conn_buf[4096];
+static size_t conn_buf_posn = 0;
+
+int fetch(unsigned char *sha1)
+{
+ int ret;
+ signed char remote;
+ struct object_list *temp;
+
+ if (memcmp(sha1, in_transit->item->sha1, 20)) {
+ // we must have already fetched it to clean the queue
+ return has_sha1_file(sha1) ? 0 : -1;
+ }
+ prefetches--;
+ temp = in_transit;
+ in_transit = in_transit->next;
+ if (!in_transit)
+ end_of_transit = &in_transit;
+ free(temp);
+
+ if (conn_buf_posn) {
+ remote = conn_buf[0];
+ memmove(conn_buf, conn_buf + 1, --conn_buf_posn);
+ } else {
+ if (read(fd_in, &remote, 1) < 1)
+ return -1;
+ }
+ //fprintf(stderr, "Got %d\n", remote);
+ if (remote < 0)
+ return remote;
+ ret = write_sha1_from_fd(sha1, fd_in, conn_buf, 4096, &conn_buf_posn);
+ if (!ret)
+ pull_say("got %s\n", sha1_to_hex(sha1));
+ return ret;
+}
+
+static int get_version(void)
+{
+ char type = 'v';
+ write(fd_out, &type, 1);
+ write(fd_out, &local_version, 1);
+ if (read(fd_in, &remote_version, 1) < 1) {
+ return error("Couldn't read version from remote end");
+ }
+ return 0;
+}
+
+int fetch_ref(char *ref, unsigned char *sha1)
+{
+ signed char remote;
+ char type = 'r';
+ write(fd_out, &type, 1);
+ write(fd_out, ref, strlen(ref) + 1);
+ read(fd_in, &remote, 1);
+ if (remote < 0)
+ return remote;
+ read(fd_in, sha1, 20);
+ return 0;
+}
+
+static const char ssh_fetch_usage[] =
+ MY_PROGRAM_NAME
+ " [-c] [-t] [-a] [-v] [-d] [--recover] [-w ref] commit-id url";
+int main(int argc, char **argv)
+{
+ char *commit_id;
+ char *url;
+ int arg = 1;
+ const char *prog;
+
+ prog = getenv("GIT_SSH_PUSH");
+ if (!prog) prog = "git-ssh-upload";
+
+ while (arg < argc && argv[arg][0] == '-') {
+ if (argv[arg][1] == 't') {
+ get_tree = 1;
+ } else if (argv[arg][1] == 'c') {
+ get_history = 1;
+ } else if (argv[arg][1] == 'a') {
+ get_all = 1;
+ get_tree = 1;
+ get_history = 1;
+ } else if (argv[arg][1] == 'v') {
+ get_verbosely = 1;
+ } else if (argv[arg][1] == 'w') {
+ write_ref = argv[arg + 1];
+ arg++;
+ } else if (!strcmp(argv[arg], "--recover")) {
+ get_recover = 1;
+ }
+ arg++;
+ }
+ if (argc < arg + 2) {
+ usage(ssh_fetch_usage);
+ return 1;
+ }
+ commit_id = argv[arg];
+ url = argv[arg + 1];
+
+ if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
+ return 1;
+
+ if (get_version())
+ return 1;
+
+ if (pull(commit_id))
+ return 1;
+
+ return 0;
+}
diff --git a/ssh-pull.c b/ssh-pull.c
new file mode 100644
index 000000000..868ce4d41
--- /dev/null
+++ b/ssh-pull.c
@@ -0,0 +1,4 @@
+#define COUNTERPART_ENV_NAME "GIT_SSH_PUSH"
+#define COUNTERPART_PROGRAM_NAME "git-ssh-push"
+#define MY_PROGRAM_NAME "git-ssh-pull"
+#include "ssh-fetch.c"
diff --git a/ssh-push.c b/ssh-push.c
new file mode 100644
index 000000000..a562df1b3
--- /dev/null
+++ b/ssh-push.c
@@ -0,0 +1,4 @@
+#define COUNTERPART_ENV_NAME "GIT_SSH_PULL"
+#define COUNTERPART_PROGRAM_NAME "git-ssh-pull"
+#define MY_PROGRAM_NAME "git-ssh-push"
+#include "ssh-upload.c"
diff --git a/ssh-upload.c b/ssh-upload.c
new file mode 100644
index 000000000..603abcc8c
--- /dev/null
+++ b/ssh-upload.c
@@ -0,0 +1,143 @@
+#ifndef COUNTERPART_ENV_NAME
+#define COUNTERPART_ENV_NAME "GIT_SSH_FETCH"
+#endif
+#ifndef COUNTERPART_PROGRAM_NAME
+#define COUNTERPART_PROGRAM_NAME "git-ssh-fetch"
+#endif
+#ifndef MY_PROGRAM_NAME
+#define MY_PROGRAM_NAME "git-ssh-upload"
+#endif
+
+#include "cache.h"
+#include "rsh.h"
+#include "refs.h"
+
+#include <string.h>
+
+static unsigned char local_version = 1;
+static unsigned char remote_version = 0;
+
+static int verbose = 0;
+
+static int serve_object(int fd_in, int fd_out) {
+ ssize_t size;
+ unsigned char sha1[20];
+ signed char remote;
+ int posn = 0;
+ do {
+ size = read(fd_in, sha1 + posn, 20 - posn);
+ if (size < 0) {
+ perror("git-ssh-upload: read ");
+ return -1;
+ }
+ if (!size)
+ return -1;
+ posn += size;
+ } while (posn < 20);
+
+ if (verbose)
+ fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1));
+
+ remote = 0;
+
+ if (!has_sha1_file(sha1)) {
+ fprintf(stderr, "git-ssh-upload: could not find %s\n",
+ sha1_to_hex(sha1));
+ remote = -1;
+ }
+
+ write(fd_out, &remote, 1);
+
+ if (remote < 0)
+ return 0;
+
+ return write_sha1_to_fd(fd_out, sha1);
+}
+
+static int serve_version(int fd_in, int fd_out)
+{
+ if (read(fd_in, &remote_version, 1) < 1)
+ return -1;
+ write(fd_out, &local_version, 1);
+ return 0;
+}
+
+static int serve_ref(int fd_in, int fd_out)
+{
+ char ref[PATH_MAX];
+ unsigned char sha1[20];
+ int posn = 0;
+ signed char remote = 0;
+ do {
+ if (read(fd_in, ref + posn, 1) < 1)
+ return -1;
+ posn++;
+ } while (ref[posn - 1]);
+
+ if (verbose)
+ fprintf(stderr, "Serving %s\n", ref);
+
+ if (get_ref_sha1(ref, sha1))
+ remote = -1;
+ write(fd_out, &remote, 1);
+ if (remote)
+ return 0;
+ write(fd_out, sha1, 20);
+ return 0;
+}
+
+
+static void service(int fd_in, int fd_out) {
+ char type;
+ int retval;
+ do {
+ retval = read(fd_in, &type, 1);
+ if (retval < 1) {
+ if (retval < 0)
+ perror("git-ssh-upload: read ");
+ return;
+ }
+ if (type == 'v' && serve_version(fd_in, fd_out))
+ return;
+ if (type == 'o' && serve_object(fd_in, fd_out))
+ return;
+ if (type == 'r' && serve_ref(fd_in, fd_out))
+ return;
+ } while (1);
+}
+
+static const char ssh_push_usage[] =
+ MY_PROGRAM_NAME " [-c] [-t] [-a] [-w ref] commit-id url";
+
+int main(int argc, char **argv)
+{
+ int arg = 1;
+ char *commit_id;
+ char *url;
+ int fd_in, fd_out;
+ const char *prog;
+ unsigned char sha1[20];
+ char hex[41];
+
+ prog = getenv(COUNTERPART_ENV_NAME);
+ if (!prog) prog = COUNTERPART_PROGRAM_NAME;
+ while (arg < argc && argv[arg][0] == '-') {
+ if (argv[arg][1] == 'w')
+ arg++;
+ arg++;
+ }
+ if (argc < arg + 2)
+ usage(ssh_push_usage);
+ commit_id = argv[arg];
+ url = argv[arg + 1];
+ if (get_sha1(commit_id, sha1))
+ usage(ssh_push_usage);
+ memcpy(hex, sha1_to_hex(sha1), sizeof(hex));
+ argv[arg] = hex;
+
+ if (setup_connection(&fd_in, &fd_out, prog, url, arg, argv + 1))
+ return 1;
+
+ service(fd_in, fd_out);
+ return 0;
+}
diff --git a/strbuf.c b/strbuf.c
new file mode 100644
index 000000000..9d9d8bed9
--- /dev/null
+++ b/strbuf.c
@@ -0,0 +1,44 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include "strbuf.h"
+#include "cache.h"
+
+void strbuf_init(struct strbuf *sb) {
+ sb->buf = NULL;
+ sb->eof = sb->alloc = sb->len = 0;
+}
+
+static void strbuf_begin(struct strbuf *sb) {
+ free(sb->buf);
+ strbuf_init(sb);
+}
+
+static void inline strbuf_add(struct strbuf *sb, int ch) {
+ if (sb->alloc <= sb->len) {
+ sb->alloc = sb->alloc * 3 / 2 + 16;
+ sb->buf = xrealloc(sb->buf, sb->alloc);
+ }
+ sb->buf[sb->len++] = ch;
+}
+
+static void strbuf_end(struct strbuf *sb) {
+ strbuf_add(sb, 0);
+}
+
+void read_line(struct strbuf *sb, FILE *fp, int term) {
+ int ch;
+ strbuf_begin(sb);
+ if (feof(fp)) {
+ sb->eof = 1;
+ return;
+ }
+ while ((ch = fgetc(fp)) != EOF) {
+ if (ch == term)
+ break;
+ strbuf_add(sb, ch);
+ }
+ if (ch == EOF && sb->len == 0)
+ sb->eof = 1;
+ strbuf_end(sb);
+}
+
diff --git a/strbuf.h b/strbuf.h
new file mode 100644
index 000000000..74cc012c2
--- /dev/null
+++ b/strbuf.h
@@ -0,0 +1,13 @@
+#ifndef STRBUF_H
+#define STRBUF_H
+struct strbuf {
+ int alloc;
+ int len;
+ int eof;
+ char *buf;
+};
+
+extern void strbuf_init(struct strbuf *);
+extern void read_line(struct strbuf *, FILE *, int);
+
+#endif /* STRBUF_H */
diff --git a/stripspace.c b/stripspace.c
new file mode 100644
index 000000000..96cd0a88f
--- /dev/null
+++ b/stripspace.c
@@ -0,0 +1,48 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+/*
+ * Remove empty lines from the beginning and end.
+ *
+ * Turn multiple consecutive empty lines into just one
+ * empty line.
+ */
+static void cleanup(char *line)
+{
+ int len = strlen(line);
+
+ if (len > 1 && line[len-1] == '\n') {
+ do {
+ unsigned char c = line[len-2];
+ if (!isspace(c))
+ break;
+ line[len-2] = '\n';
+ len--;
+ line[len] = 0;
+ } while (len > 1);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ int empties = -1;
+ char line[1024];
+
+ while (fgets(line, sizeof(line), stdin)) {
+ cleanup(line);
+
+ /* Not just an empty line? */
+ if (line[0] != '\n') {
+ if (empties > 0)
+ putchar('\n');
+ empties = 0;
+ fputs(line, stdout);
+ continue;
+ }
+ if (empties < 0)
+ continue;
+ empties++;
+ }
+ return 0;
+}
diff --git a/symbolic-ref.c b/symbolic-ref.c
new file mode 100644
index 000000000..193c87c17
--- /dev/null
+++ b/symbolic-ref.c
@@ -0,0 +1,35 @@
+#include "cache.h"
+
+static const char git_symbolic_ref_usage[] =
+"git-symbolic-ref name [ref]";
+
+static void check_symref(const char *HEAD)
+{
+ unsigned char sha1[20];
+ const char *git_HEAD = strdup(git_path("%s", HEAD));
+ const char *git_refs_heads_master = resolve_ref(git_HEAD, sha1, 0);
+ if (git_refs_heads_master) {
+ /* we want to strip the .git/ part */
+ int pfxlen = strlen(git_HEAD) - strlen(HEAD);
+ puts(git_refs_heads_master + pfxlen);
+ }
+ else
+ die("No such ref: %s", HEAD);
+}
+
+int main(int argc, const char **argv)
+{
+ setup_git_directory();
+ git_config(git_default_config);
+ switch (argc) {
+ case 2:
+ check_symref(argv[1]);
+ break;
+ case 3:
+ create_symref(strdup(git_path("%s", argv[1])), argv[2]);
+ break;
+ default:
+ usage(git_symbolic_ref_usage);
+ }
+ return 0;
+}
diff --git a/t/Makefile b/t/Makefile
new file mode 100644
index 000000000..5c5a62012
--- /dev/null
+++ b/t/Makefile
@@ -0,0 +1,28 @@
+# Run tests
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+#GIT_TEST_OPTS=--verbose --debug
+SHELL_PATH ?= $(SHELL)
+TAR ?= $(TAR)
+
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+all: $(T) clean
+
+$(T):
+ @echo "*** $@ ***"; $(call shellquote,$(SHELL_PATH)) $@ $(GIT_TEST_OPTS)
+
+clean:
+ rm -fr trash
+
+.PHONY: $(T) clean
+.NOPARALLEL:
+
diff --git a/t/README b/t/README
new file mode 100644
index 000000000..ac5a3ac56
--- /dev/null
+++ b/t/README
@@ -0,0 +1,208 @@
+Core GIT Tests
+==============
+
+This directory holds many test scripts for core GIT tools. The
+first part of this short document describes how to run the tests
+and read their output.
+
+When fixing the tools or adding enhancements, you are strongly
+encouraged to add tests in this directory to cover what you are
+trying to fix or enhance. The later part of this short document
+describes how your test scripts should be organized.
+
+
+Running Tests
+-------------
+
+The easiest way to run tests is to say "make". This runs all
+the tests.
+
+ *** t0000-basic.sh ***
+ * ok 1: .git/objects should be empty after git-init-db in an empty repo.
+ * ok 2: .git/objects should have 256 subdirectories.
+ * ok 3: git-update-index without --add should fail adding.
+ ...
+ * ok 23: no diff after checkout and git-update-index --refresh.
+ * passed all 23 test(s)
+ *** t0100-environment-names.sh ***
+ * ok 1: using old names should issue warnings.
+ * ok 2: using old names but having new names should not issue warnings.
+ ...
+
+Or you can run each test individually from command line, like
+this:
+
+ $ sh ./t3001-ls-files-killed.sh
+ * ok 1: git-update-index --add to add various paths.
+ * ok 2: git-ls-files -k to show killed files.
+ * ok 3: validate git-ls-files -k output.
+ * passed all 3 test(s)
+
+You can pass --verbose (or -v), --debug (or -d), and --immediate
+(or -i) command line argument to the test.
+
+--verbose::
+ This makes the test more verbose. Specifically, the
+ command being run and their output if any are also
+ output.
+
+--debug::
+ This may help the person who is developing a new test.
+ It causes the command defined with test_debug to run.
+
+--immediate::
+ This causes the test to immediately exit upon the first
+ failed test.
+
+
+Naming Tests
+------------
+
+The test files are named as:
+
+ tNNNN-commandname-details.sh
+
+where N is a decimal digit.
+
+First digit tells the family:
+
+ 0 - the absolute basics and global stuff
+ 1 - the basic commands concerning database
+ 2 - the basic commands concerning the working tree
+ 3 - the other basic commands (e.g. ls-files)
+ 4 - the diff commands
+ 5 - the pull and exporting commands
+ 6 - the revision tree commands (even e.g. merge-base)
+
+Second digit tells the particular command we are testing.
+
+Third digit (optionally) tells the particular switch or group of switches
+we are testing.
+
+If you create files under t/ directory (i.e. here) that is not
+the top-level test script, never name the file to match the above
+pattern. The Makefile here considers all such files as the
+top-level test script and tries to run all of them. A care is
+especially needed if you are creating a common test library
+file, similar to test-lib.sh, because such a library file may
+not be suitable for standalone execution.
+
+
+Writing Tests
+-------------
+
+The test script is written as a shell script. It should start
+with the standard "#!/bin/sh" with copyright notices, and an
+assignment to variable 'test_description', like this:
+
+ #!/bin/sh
+ #
+ # Copyright (c) 2005 Junio C Hamano
+ #
+
+ test_description='xxx test (option --frotz)
+
+ This test registers the following structure in the cache
+ and tries to run git-ls-files with option --frotz.'
+
+
+Source 'test-lib.sh'
+--------------------
+
+After assigning test_description, the test script should source
+test-lib.sh like this:
+
+ . ./test-lib.sh
+
+This test harness library does the following things:
+
+ - If the script is invoked with command line argument --help
+ (or -h), it shows the test_description and exits.
+
+ - Creates an empty test directory with an empty .git/objects
+ database and chdir(2) into it. This directory is 't/trash'
+ if you must know, but I do not think you care.
+
+ - Defines standard test helper functions for your scripts to
+ use. These functions are designed to make all scripts behave
+ consistently when command line arguments --verbose (or -v),
+ --debug (or -d), and --immediate (or -i) is given.
+
+
+End with test_done
+------------------
+
+Your script will be a sequence of tests, using helper functions
+from the test harness library. At the end of the script, call
+'test_done'.
+
+
+Test harness library
+--------------------
+
+There are a handful helper functions defined in the test harness
+library for your script to use.
+
+ - test_expect_success <message> <script>
+
+ This takes two strings as parameter, and evaluates the
+ <script>. If it yields success, test is considered
+ successful. <message> should state what it is testing.
+
+ Example:
+
+ test_expect_success \
+ 'git-write-tree should be able to write an empty tree.' \
+ 'tree=$(git-write-tree)'
+
+ - test_expect_failure <message> <script>
+
+ This is the opposite of test_expect_success. If <script>
+ yields success, test is considered a failure.
+
+ Example:
+
+ test_expect_failure \
+ 'git-update-index without --add should fail adding.' \
+ 'git-update-index should-be-empty'
+
+ - test_debug <script>
+
+ This takes a single argument, <script>, and evaluates it only
+ when the test script is started with --debug command line
+ argument. This is primarily meant for use during the
+ development of a new test script.
+
+ - test_done
+
+ Your test script must have test_done at the end. Its purpose
+ is to summarize successes and failures in the test script and
+ exit with an appropriate error code.
+
+
+Tips for Writing Tests
+----------------------
+
+As with any programming projects, existing programs are the best
+source of the information. However, do _not_ emulate
+t0000-basic.sh when writing your tests. The test is special in
+that it tries to validate the very core of GIT. For example, it
+knows that there will be 256 subdirectories under .git/objects/,
+and it knows that the object ID of an empty tree is a certain
+40-byte string. This is deliberately done so in t0000-basic.sh
+because the things the very basic core test tries to achieve is
+to serve as a basis for people who are changing the GIT internal
+drastically. For these people, after making certain changes,
+not seeing failures from the basic test _is_ a failure. And
+such drastic changes to the core GIT that even changes these
+otherwise supposedly stable object IDs should be accompanied by
+an update to t0000-basic.sh.
+
+However, other tests that simply rely on basic parts of the core
+GIT working properly should not have that level of intimate
+knowledge of the core GIT internals. If all the test scripts
+hardcoded the object IDs like t0000-basic.sh does, that defeats
+the purpose of t0000-basic.sh, which is to isolate that level of
+validation in one place. Your test also ends up needing
+updating when such a change to the internal happens, so do _not_
+do it and leave the low level of validation to t0000-basic.sh.
diff --git a/t/diff-lib.sh b/t/diff-lib.sh
new file mode 100755
index 000000000..745a1b031
--- /dev/null
+++ b/t/diff-lib.sh
@@ -0,0 +1,41 @@
+:
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+sanitize_diff_raw='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]* / X X \1# /'
+compare_diff_raw () {
+ # When heuristics are improved, the score numbers would change.
+ # Ignore them while comparing.
+ # Also we do not check SHA1 hash generation in this test, which
+ # is a job for t0000-basic.sh
+
+ sed -e "$sanitize_diff_raw" <"$1" >.tmp-1
+ sed -e "$sanitize_diff_raw" <"$2" >.tmp-2
+ diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
+
+sanitize_diff_raw_z='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*$/ X X \1#/'
+compare_diff_raw_z () {
+ # When heuristics are improved, the score numbers would change.
+ # Ignore them while comparing.
+ # Also we do not check SHA1 hash generation in this test, which
+ # is a job for t0000-basic.sh
+
+ tr '\0' '\012' <"$1" | sed -e "$sanitize_diff_raw_z" >.tmp-1
+ tr '\0' '\012' <"$2" | sed -e "$sanitize_diff_raw_z" >.tmp-2
+ diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
+
+compare_diff_patch () {
+ # When heuristics are improved, the score numbers would change.
+ # Ignore them while comparing.
+ sed -e '
+ /^[dis]*imilarity index [0-9]*%$/d
+ /^index [0-9a-f]*\.\.[0-9a-f]/d
+ ' <"$1" >.tmp-1
+ sed -e '
+ /^[dis]*imilarity index [0-9]*%$/d
+ /^index [0-9a-f]*\.\.[0-9a-f]/d
+ ' <"$2" >.tmp-2
+ diff -u .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2
+}
diff --git a/t/lib-read-tree-m-3way.sh b/t/lib-read-tree-m-3way.sh
new file mode 100755
index 000000000..d195603df
--- /dev/null
+++ b/t/lib-read-tree-m-3way.sh
@@ -0,0 +1,158 @@
+: Included from t1000-read-tree-m-3way.sh and others
+# Original tree.
+mkdir Z
+for a in N D M
+do
+ for b in N D M
+ do
+ p=$a$b
+ echo This is $p from the original tree. >$p
+ echo This is Z/$p from the original tree. >Z/$p
+ test_expect_success \
+ "adding test file $p and Z/$p" \
+ 'git-update-index --add $p &&
+ git-update-index --add Z/$p'
+ done
+done
+echo This is SS from the original tree. >SS
+test_expect_success \
+ 'adding test file SS' \
+ 'git-update-index --add SS'
+cat >TT <<\EOF
+This is a trivial merge sample text.
+Branch A is expected to upcase this word, here.
+There are some filler lines to avoid diff context
+conflicts here,
+like this one,
+and this one,
+and this one is yet another one of them.
+At the very end, here comes another line, that is
+the word, expected to be upcased by Branch B.
+This concludes the trivial merge sample file.
+EOF
+test_expect_success \
+ 'adding test file TT' \
+ 'git-update-index --add TT'
+test_expect_success \
+ 'prepare initial tree' \
+ 'tree_O=$(git-write-tree)'
+
+################################################################
+# Branch A and B makes the changes according to the above matrix.
+
+################################################################
+# Branch A
+
+to_remove=$(echo D? Z/D?)
+rm -f $to_remove
+test_expect_success \
+ 'change in branch A (removal)' \
+ 'git-update-index --remove $to_remove'
+
+for p in M? Z/M?
+do
+ echo This is modified $p in the branch A. >$p
+ test_expect_success \
+ 'change in branch A (modification)' \
+ "git-update-index $p"
+done
+
+for p in AN AA Z/AN Z/AA
+do
+ echo This is added $p in the branch A. >$p
+ test_expect_success \
+ 'change in branch A (addition)' \
+ "git-update-index --add $p"
+done
+
+echo This is SS from the modified tree. >SS
+echo This is LL from the modified tree. >LL
+test_expect_success \
+ 'change in branch A (addition)' \
+ 'git-update-index --add LL &&
+ git-update-index SS'
+mv TT TT-
+sed -e '/Branch A/s/word/WORD/g' <TT- >TT
+rm -f TT-
+test_expect_success \
+ 'change in branch A (edit)' \
+ 'git-update-index TT'
+
+mkdir DF
+echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF
+test_expect_success \
+ 'change in branch A (change file to directory)' \
+ 'git-update-index --add DF/DF'
+
+test_expect_success \
+ 'recording branch A tree' \
+ 'tree_A=$(git-write-tree)'
+
+################################################################
+# Branch B
+# Start from O
+
+rm -rf [NDMASLT][NDMASLT] Z DF
+mkdir Z
+test_expect_success \
+ 'reading original tree and checking out' \
+ 'git-read-tree $tree_O &&
+ git-checkout-index -a'
+
+to_remove=$(echo ?D Z/?D)
+rm -f $to_remove
+test_expect_success \
+ 'change in branch B (removal)' \
+ "git-update-index --remove $to_remove"
+
+for p in ?M Z/?M
+do
+ echo This is modified $p in the branch B. >$p
+ test_expect_success \
+ 'change in branch B (modification)' \
+ "git-update-index $p"
+done
+
+for p in NA AA Z/NA Z/AA
+do
+ echo This is added $p in the branch B. >$p
+ test_expect_success \
+ 'change in branch B (addition)' \
+ "git-update-index --add $p"
+done
+echo This is SS from the modified tree. >SS
+echo This is LL from the modified tree. >LL
+test_expect_success \
+ 'change in branch B (addition and modification)' \
+ 'git-update-index --add LL &&
+ git-update-index SS'
+mv TT TT-
+sed -e '/Branch B/s/word/WORD/g' <TT- >TT
+rm -f TT-
+test_expect_success \
+ 'change in branch B (modification)' \
+ 'git-update-index TT'
+
+echo Branch B makes a file at DF. >DF
+test_expect_success \
+ 'change in branch B (addition of a file to conflict with directory)' \
+ 'git-update-index --add DF'
+
+test_expect_success \
+ 'recording branch B tree' \
+ 'tree_B=$(git-write-tree)'
+
+test_expect_success \
+ 'keep contents of 3 trees for easy access' \
+ 'rm -f .git/index &&
+ git-read-tree $tree_O &&
+ mkdir .orig-O &&
+ git-checkout-index --prefix=.orig-O/ -f -q -a &&
+ rm -f .git/index &&
+ git-read-tree $tree_A &&
+ mkdir .orig-A &&
+ git-checkout-index --prefix=.orig-A/ -f -q -a &&
+ rm -f .git/index &&
+ git-read-tree $tree_B &&
+ mkdir .orig-B &&
+ git-checkout-index --prefix=.orig-B/ -f -q -a'
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
new file mode 100755
index 000000000..dff7d6916
--- /dev/null
+++ b/t/t0000-basic.sh
@@ -0,0 +1,180 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test the very basics part #1.
+
+The rest of the test suite does not check the basic operation of git
+plumbing commands to work very carefully. Their job is to concentrate
+on tricky features that caused bugs in the past to detect regression.
+
+This test runs very basic features, like registering things in cache,
+writing tree, etc.
+
+Note that this test *deliberately* hard-codes many expected object
+IDs. When object ID computation changes, like in the previous case of
+swapping compression and hashing order, the person who is making the
+modification *should* take notice and update the test vectors here.
+'
+. ./test-lib.sh
+
+################################################################
+# init-db has been done in an empty repository.
+# make sure it is empty.
+
+find .git/objects -type f -print >should-be-empty
+test_expect_success \
+ '.git/objects should be empty after git-init-db in an empty repo.' \
+ 'cmp -s /dev/null should-be-empty'
+
+# also it should have 2 subdirectories; no fan-out anymore, pack, and info.
+# 3 is counting "objects" itself
+find .git/objects -type d -print >full-of-directories
+test_expect_success \
+ '.git/objects should have 3 subdirectories.' \
+ 'test $(wc -l < full-of-directories) = 3'
+
+################################################################
+# Basics of the basics
+
+# updating a new file without --add should fail.
+test_expect_failure \
+ 'git-update-index without --add should fail adding.' \
+ 'git-update-index should-be-empty'
+
+# and with --add it should succeed, even if it is empty (it used to fail).
+test_expect_success \
+ 'git-update-index with --add should succeed.' \
+ 'git-update-index --add should-be-empty'
+
+test_expect_success \
+ 'writing tree out with git-write-tree' \
+ 'tree=$(git-write-tree)'
+
+# we know the shape and contents of the tree and know the object ID for it.
+test_expect_success \
+ 'validate object ID of a known tree.' \
+ 'test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a'
+
+# Removing paths.
+rm -f should-be-empty full-of-directories
+test_expect_failure \
+ 'git-update-index without --remove should fail removing.' \
+ 'git-update-index should-be-empty'
+
+test_expect_success \
+ 'git-update-index with --remove should be able to remove.' \
+ 'git-update-index --remove should-be-empty'
+
+# Empty tree can be written with recent write-tree.
+test_expect_success \
+ 'git-write-tree should be able to write an empty tree.' \
+ 'tree=$(git-write-tree)'
+
+test_expect_success \
+ 'validate object ID of a known tree.' \
+ 'test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904'
+
+# Various types of objects
+mkdir path2 path3 path3/subp3
+for p in path0 path2/file2 path3/file3 path3/subp3/file3
+do
+ echo "hello $p" >$p
+ ln -s "hello $p" ${p}sym
+done
+test_expect_success \
+ 'adding various types of objects with git-update-index --add.' \
+ 'find path* ! -type d -print | xargs git-update-index --add'
+
+# Show them and see that matches what we expect.
+test_expect_success \
+ 'showing stage with git-ls-files --stage' \
+ 'git-ls-files --stage >current'
+
+cat >expected <<\EOF
+100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0
+120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym
+100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0 path2/file2
+120000 d8ce161addc5173867a3c3c730924388daedbc38 0 path2/file2sym
+100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0 path3/file3
+120000 8599103969b43aff7e430efea79ca4636466794f 0 path3/file3sym
+100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0 path3/subp3/file3
+120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0 path3/subp3/file3sym
+EOF
+test_expect_success \
+ 'validate git-ls-files output for a known tree.' \
+ 'diff current expected'
+
+test_expect_success \
+ 'writing tree out with git-write-tree.' \
+ 'tree=$(git-write-tree)'
+test_expect_success \
+ 'validate object ID for a known tree.' \
+ 'test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b'
+
+test_expect_success \
+ 'showing tree with git-ls-tree' \
+ 'git-ls-tree $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
+EOF
+test_expect_success \
+ 'git-ls-tree output for a known tree.' \
+ 'diff current expected'
+
+test_expect_success \
+ 'showing tree with git-ls-tree -r' \
+ 'git-ls-tree -r $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2
+100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2
+120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3
+100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3
+120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym
+040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3
+100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3
+120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym
+EOF
+test_expect_success \
+ 'git-ls-tree -r output for a known tree.' \
+ 'diff current expected'
+
+################################################################
+rm .git/index
+test_expect_success \
+ 'git-read-tree followed by write-tree should be idempotent.' \
+ 'git-read-tree $tree &&
+ test -f .git/index &&
+ newtree=$(git-write-tree) &&
+ test "$newtree" = "$tree"'
+
+cat >expected <<\EOF
+:100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M path0
+:120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M path0sym
+:100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M path2/file2
+:120000 120000 d8ce161addc5173867a3c3c730924388daedbc38 0000000000000000000000000000000000000000 M path2/file2sym
+:100644 100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0000000000000000000000000000000000000000 M path3/file3
+:120000 120000 8599103969b43aff7e430efea79ca4636466794f 0000000000000000000000000000000000000000 M path3/file3sym
+:100644 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0000000000000000000000000000000000000000 M path3/subp3/file3
+:120000 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0000000000000000000000000000000000000000 M path3/subp3/file3sym
+EOF
+test_expect_success \
+ 'validate git-diff-files output for a know cache/work tree state.' \
+ 'git-diff-files >current && diff >/dev/null -b current expected'
+
+test_expect_success \
+ 'git-update-index --refresh should succeed.' \
+ 'git-update-index --refresh'
+
+test_expect_success \
+ 'no diff after checkout and git-update-index --refresh.' \
+ 'git-diff-files >current && cmp -s current /dev/null'
+
+test_done
diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh
new file mode 100755
index 000000000..d0af8c3d5
--- /dev/null
+++ b/t/t1000-read-tree-m-3way.sh
@@ -0,0 +1,514 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Three way merge with read-tree -m
+
+This test tries three-way merge with read-tree -m
+
+There is one ancestor (called O for Original) and two branches A
+and B derived from it. We want to do a 3-way merge between A and
+B, using O as the common ancestor.
+
+ merge A O B
+
+Decisions are made by comparing contents of O, A and B pathname
+by pathname. The result is determined by the following guiding
+principle:
+
+ - If only A does something to it and B does not touch it, take
+ whatever A does.
+
+ - If only B does something to it and A does not touch it, take
+ whatever B does.
+
+ - If both A and B does something but in the same way, take
+ whatever they do.
+
+ - If A and B does something but different things, we need a
+ 3-way merge:
+
+ - We cannot do anything about the following cases:
+
+ * O does not have it. A and B both must be adding to the
+ same path independently.
+
+ * A deletes it. B must be modifying.
+
+ - Otherwise, A and B are modifying. Run 3-way merge.
+
+First, the case matrix.
+
+ - Vertical axis is for A'\''s actions.
+ - Horizontal axis is for B'\''s actions.
+
+.----------------------------------------------------------------.
+| A B | No Action | Delete | Modify | Add |
+|------------+------------+------------+------------+------------|
+| No Action | | | | |
+| | select O | delete | select B | select B |
+| | | | | |
+|------------+------------+------------+------------+------------|
+| Delete | | | ********** | can |
+| | delete | delete | merge | not |
+| | | | | happen |
+|------------+------------+------------+------------+------------|
+| Modify | | ********** | ?????????? | can |
+| | select A | merge | select A=B | not |
+| | | | merge | happen |
+|------------+------------+------------+------------+------------|
+| Add | | can | can | ?????????? |
+| | select A | not | not | select A=B |
+| | | happen | happen | merge |
+.----------------------------------------------------------------.
+
+In addition:
+
+ SS: a special case of MM, where A and B makes the same modification.
+ LL: a special case of AA, where A and B creates the same file.
+ TT: a special case of MM, where A and B makes mergeable changes.
+ DF: a special case, where A makes a directory and B makes a file.
+
+'
+. ./test-lib.sh
+. ../lib-read-tree-m-3way.sh
+
+################################################################
+# Trivial "majority when 3 stages exist" merge plus #2ALT, #3ALT
+# and #5ALT trivial merges.
+
+cat >expected <<\EOF
+100644 X 2 AA
+100644 X 3 AA
+100644 X 0 AN
+100644 X 1 DD
+100644 X 3 DF
+100644 X 2 DF/DF
+100644 X 1 DM
+100644 X 3 DM
+100644 X 1 DN
+100644 X 3 DN
+100644 X 0 LL
+100644 X 1 MD
+100644 X 2 MD
+100644 X 1 MM
+100644 X 2 MM
+100644 X 3 MM
+100644 X 0 MN
+100644 X 0 NA
+100644 X 1 ND
+100644 X 2 ND
+100644 X 0 NM
+100644 X 0 NN
+100644 X 0 SS
+100644 X 1 TT
+100644 X 2 TT
+100644 X 3 TT
+100644 X 2 Z/AA
+100644 X 3 Z/AA
+100644 X 0 Z/AN
+100644 X 1 Z/DD
+100644 X 1 Z/DM
+100644 X 3 Z/DM
+100644 X 1 Z/DN
+100644 X 3 Z/DN
+100644 X 1 Z/MD
+100644 X 2 Z/MD
+100644 X 1 Z/MM
+100644 X 2 Z/MM
+100644 X 3 Z/MM
+100644 X 0 Z/MN
+100644 X 0 Z/NA
+100644 X 1 Z/ND
+100644 X 2 Z/ND
+100644 X 0 Z/NM
+100644 X 0 Z/NN
+EOF
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+
+check_result () {
+ git-ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
+ diff -u expected current
+}
+
+# This is done on an empty work directory, which is the normal
+# merge person behaviour.
+test_expect_success \
+ '3-way merge with git-read-tree -m, empty cache' \
+ "rm -fr [NDMALTS][NDMALTSF] Z &&
+ rm .git/index &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+# This starts out with the first head, which is the normal
+# patch submitter behaviour.
+test_expect_success \
+ '3-way merge with git-read-tree -m, match H' \
+ "rm -fr [NDMALTS][NDMALTSF] Z &&
+ rm .git/index &&
+ git-read-tree $tree_A &&
+ git-checkout-index -f -u -a &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+: <<\END_OF_CASE_TABLE
+
+We have so far tested only empty index and clean-and-matching-A index
+case which are trivial. Make sure index requirements are also
+checked.
+
+"git-read-tree -m O A B"
+
+ O A B result index requirements
+-------------------------------------------------------------------
+ 1 missing missing missing - must not exist.
+ ------------------------------------------------------------------
+ 2 missing missing exists take B* must match B, if exists.
+ ------------------------------------------------------------------
+ 3 missing exists missing take A* must match A, if exists.
+ ------------------------------------------------------------------
+ 4 missing exists A!=B no merge must match A and be
+ up-to-date, if exists.
+ ------------------------------------------------------------------
+ 5 missing exists A==B take A must match A, if exists.
+ ------------------------------------------------------------------
+ 6 exists missing missing remove must not exist.
+ ------------------------------------------------------------------
+ 7 exists missing O!=B no merge must not exist.
+ ------------------------------------------------------------------
+ 8 exists missing O==B remove must not exist.
+ ------------------------------------------------------------------
+ 9 exists O!=A missing no merge must match A and be
+ up-to-date, if exists.
+ ------------------------------------------------------------------
+ 10 exists O==A missing remove ditto
+ ------------------------------------------------------------------
+ 11 exists O!=A O!=B no merge must match A and be
+ A!=B up-to-date, if exists.
+ ------------------------------------------------------------------
+ 12 exists O!=A O!=B take A must match A, if exists.
+ A==B
+ ------------------------------------------------------------------
+ 13 exists O!=A O==B take A must match A, if exists.
+ ------------------------------------------------------------------
+ 14 exists O==A O!=B take B if exists, must either (1)
+ match A and be up-to-date,
+ or (2) match B.
+ ------------------------------------------------------------------
+ 15 exists O==A O==B take B must match A if exists.
+ ------------------------------------------------------------------
+ 16 exists O==A O==B barf must match A if exists.
+ *multi* in one in another
+-------------------------------------------------------------------
+
+Note: we need to be careful in case 2 and 3. The tree A may contain
+DF (file) when tree B require DF to be a directory by having DF/DF
+(file).
+
+END_OF_CASE_TABLE
+
+test_expect_failure \
+ '1 - must not have an entry not in A.' \
+ "rm -f .git/index XX &&
+ echo XX >XX &&
+ git-update-index --add XX &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '2 - must match B in !O && !A && B case.' \
+ "rm -f .git/index NA &&
+ cp .orig-B/NA NA &&
+ git-update-index --add NA &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '2 - matching B alone is OK in !O && !A && B case.' \
+ "rm -f .git/index NA &&
+ cp .orig-B/NA NA &&
+ git-update-index --add NA &&
+ echo extra >>NA &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '3 - must match A in !O && A && !B case.' \
+ "rm -f .git/index AN &&
+ cp .orig-A/AN AN &&
+ git-update-index --add AN &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_success \
+ '3 - matching A alone is OK in !O && A && !B case.' \
+ "rm -f .git/index AN &&
+ cp .orig-A/AN AN &&
+ git-update-index --add AN &&
+ echo extra >>AN &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '3 (fail) - must match A in !O && A && !B case.' \
+ "rm -f .git/index AN &&
+ cp .orig-A/AN AN &&
+ echo extra >>AN &&
+ git-update-index --add AN &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '4 - must match and be up-to-date in !O && A && B && A!=B case.' \
+ "rm -f .git/index AA &&
+ cp .orig-A/AA AA &&
+ git-update-index --add AA &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_failure \
+ '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
+ "rm -f .git/index AA &&
+ cp .orig-A/AA AA &&
+ git-update-index --add AA &&
+ echo extra >>AA &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
+ "rm -f .git/index AA &&
+ cp .orig-A/AA AA &&
+ echo extra >>AA &&
+ git-update-index --add AA &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '5 - must match in !O && A && B && A==B case.' \
+ "rm -f .git/index LL &&
+ cp .orig-A/LL LL &&
+ git-update-index --add LL &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_success \
+ '5 - must match in !O && A && B && A==B case.' \
+ "rm -f .git/index LL &&
+ cp .orig-A/LL LL &&
+ git-update-index --add LL &&
+ echo extra >>LL &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_failure \
+ '5 (fail) - must match A in !O && A && B && A==B case.' \
+ "rm -f .git/index LL &&
+ cp .orig-A/LL LL &&
+ echo extra >>LL &&
+ git-update-index --add LL &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '6 - must not exist in O && !A && !B case' \
+ "rm -f .git/index DD &&
+ echo DD >DD
+ git-update-index --add DD &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '7 - must not exist in O && !A && B && O!=B case' \
+ "rm -f .git/index DM &&
+ cp .orig-B/DM DM &&
+ git-update-index --add DM &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '8 - must not exist in O && !A && B && O==B case' \
+ "rm -f .git/index DN &&
+ cp .orig-B/DN DN &&
+ git-update-index --add DN &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '9 - must match and be up-to-date in O && A && !B && O!=A case' \
+ "rm -f .git/index MD &&
+ cp .orig-A/MD MD &&
+ git-update-index --add MD &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_failure \
+ '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
+ "rm -f .git/index MD &&
+ cp .orig-A/MD MD &&
+ git-update-index --add MD &&
+ echo extra >>MD &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
+ "rm -f .git/index MD &&
+ cp .orig-A/MD MD &&
+ echo extra >>MD &&
+ git-update-index --add MD &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '10 - must match and be up-to-date in O && A && !B && O==A case' \
+ "rm -f .git/index ND &&
+ cp .orig-A/ND ND &&
+ git-update-index --add ND &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_failure \
+ '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
+ "rm -f .git/index ND &&
+ cp .orig-A/ND ND &&
+ git-update-index --add ND &&
+ echo extra >>ND &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
+ "rm -f .git/index ND &&
+ cp .orig-A/ND ND &&
+ echo extra >>ND &&
+ git-update-index --add ND &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+ "rm -f .git/index MM &&
+ cp .orig-A/MM MM &&
+ git-update-index --add MM &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_failure \
+ '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+ "rm -f .git/index MM &&
+ cp .orig-A/MM MM &&
+ git-update-index --add MM &&
+ echo extra >>MM &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
+ "rm -f .git/index MM &&
+ cp .orig-A/MM MM &&
+ echo extra >>MM &&
+ git-update-index --add MM &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '12 - must match A in O && A && B && O!=A && A==B case' \
+ "rm -f .git/index SS &&
+ cp .orig-A/SS SS &&
+ git-update-index --add SS &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_success \
+ '12 - must match A in O && A && B && O!=A && A==B case' \
+ "rm -f .git/index SS &&
+ cp .orig-A/SS SS &&
+ git-update-index --add SS &&
+ echo extra >>SS &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_failure \
+ '12 (fail) - must match A in O && A && B && O!=A && A==B case' \
+ "rm -f .git/index SS &&
+ cp .orig-A/SS SS &&
+ echo extra >>SS &&
+ git-update-index --add SS &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '13 - must match A in O && A && B && O!=A && O==B case' \
+ "rm -f .git/index MN &&
+ cp .orig-A/MN MN &&
+ git-update-index --add MN &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_success \
+ '13 - must match A in O && A && B && O!=A && O==B case' \
+ "rm -f .git/index MN &&
+ cp .orig-A/MN MN &&
+ git-update-index --add MN &&
+ echo extra >>MN &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_success \
+ '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+ "rm -f .git/index NM &&
+ cp .orig-A/NM NM &&
+ git-update-index --add NM &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_success \
+ '14 - may match B in O && A && B && O==A && O!=B case' \
+ "rm -f .git/index NM &&
+ cp .orig-B/NM NM &&
+ git-update-index --add NM &&
+ echo extra >>NM &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_failure \
+ '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+ "rm -f .git/index NM &&
+ cp .orig-A/NM NM &&
+ git-update-index --add NM &&
+ echo extra >>NM &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_failure \
+ '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
+ "rm -f .git/index NM &&
+ cp .orig-A/NM NM &&
+ echo extra >>NM &&
+ git-update-index --add NM &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+test_expect_success \
+ '15 - must match A in O && A && B && O==A && O==B case' \
+ "rm -f .git/index NN &&
+ cp .orig-A/NN NN &&
+ git-update-index --add NN &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_success \
+ '15 - must match A in O && A && B && O==A && O==B case' \
+ "rm -f .git/index NN &&
+ cp .orig-A/NN NN &&
+ git-update-index --add NN &&
+ echo extra >>NN &&
+ git-read-tree -m $tree_O $tree_A $tree_B &&
+ check_result"
+
+test_expect_failure \
+ '15 (fail) - must match A in O && A && B && O==A && O==B case' \
+ "rm -f .git/index NN &&
+ cp .orig-A/NN NN &&
+ echo extra >>NN &&
+ git-update-index --add NN &&
+ git-read-tree -m $tree_O $tree_A $tree_B"
+
+# #16
+test_expect_success \
+ '16 - A matches in one and B matches in another.' \
+ 'rm -f .git/index F16 &&
+ echo F16 >F16 &&
+ git-update-index --add F16 &&
+ tree0=`git-write-tree` &&
+ echo E16 >F16 &&
+ git-update-index F16 &&
+ tree1=`git-write-tree` &&
+ git-read-tree -m $tree0 $tree1 $tree1 $tree0 &&
+ git-ls-files --stage'
+
+test_done
diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh
new file mode 100755
index 000000000..d0ed24275
--- /dev/null
+++ b/t/t1001-read-tree-m-2way.sh
@@ -0,0 +1,344 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Two way merge with read-tree -m $H $M
+
+This test tries two-way merge (aka fast forward with carry forward).
+
+There is the head (called H) and another commit (called M), which is
+simply ahead of H. The index and the work tree contains a state that
+is derived from H, but may also have local changes. This test checks
+all the combinations described in the two-tree merge "carry forward"
+rules, found in <Documentation/git-read-tree.txt>.
+
+In the test, these paths are used:
+ bozbar - in H, stays in M, modified from bozbar to gnusto
+ frotz - not in H added in M
+ nitfol - in H, stays in M unmodified
+ rezrov - in H, deleted in M
+ yomin - not in H nor M
+'
+. ./test-lib.sh
+
+read_tree_twoway () {
+ git-read-tree -m "$1" "$2" && git-ls-files --stage
+}
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+compare_change () {
+ sed -n >current \
+ -e '/^--- /d; /^+++ /d; /^@@ /d;' \
+ -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \
+ "$1"
+ diff -u expected current
+}
+
+check_cache_at () {
+ clean_if_empty=`git-diff-files "$1"`
+ case "$clean_if_empty" in
+ '') echo "$1: clean" ;;
+ ?*) echo "$1: dirty" ;;
+ esac
+ case "$2,$clean_if_empty" in
+ clean,) : ;;
+ clean,?*) false ;;
+ dirty,) false ;;
+ dirty,?*) : ;;
+ esac
+}
+
+cat >bozbar-old <<\EOF
+This is a sample file used in two-way fast forward merge
+tests. Its second line ends with a magic word bozbar
+which will be modified by the merged head to gnusto.
+It has some extra lines so that external tools can
+successfully merge independent changes made to later
+lines (such as this one), avoiding line conflicts.
+EOF
+
+sed -e 's/bozbar/gnusto (earlier bozbar)/' bozbar-old >bozbar-new
+
+test_expect_success \
+ setup \
+ 'echo frotz >frotz &&
+ echo nitfol >nitfol &&
+ cat bozbar-old >bozbar &&
+ echo rezrov >rezrov &&
+ echo yomin >yomin &&
+ git-update-index --add nitfol bozbar rezrov &&
+ treeH=`git-write-tree` &&
+ echo treeH $treeH &&
+ git-ls-tree $treeH &&
+
+ cat bozbar-new >bozbar &&
+ git-update-index --add frotz bozbar --force-remove rezrov &&
+ git-ls-files --stage >M.out &&
+ treeM=`git-write-tree` &&
+ echo treeM $treeM &&
+ git-ls-tree $treeM &&
+ git-diff-tree $treeH $treeM'
+
+test_expect_success \
+ '1, 2, 3 - no carry forward' \
+ 'rm -f .git/index &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >1-3.out &&
+ diff -u M.out 1-3.out &&
+ check_cache_at bozbar dirty &&
+ check_cache_at frotz dirty &&
+ check_cache_at nitfol dirty'
+
+echo '+100644 X 0 yomin' >expected
+
+test_expect_success \
+ '4 - carry forward local addition.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ git-update-index --add yomin &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >4.out || return 1
+ diff -u M.out 4.out >4diff.out
+ compare_change 4diff.out expected &&
+ check_cache_at yomin clean'
+
+test_expect_success \
+ '5 - carry forward local addition.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo yomin >yomin &&
+ git-update-index --add yomin &&
+ echo yomin yomin >yomin &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >5.out || return 1
+ diff -u M.out 5.out >5diff.out
+ compare_change 5diff.out expected &&
+ check_cache_at yomin dirty'
+
+test_expect_success \
+ '6 - local addition already has the same.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ git-update-index --add frotz &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >6.out &&
+ diff -u M.out 6.out &&
+ check_cache_at frotz clean'
+
+test_expect_success \
+ '7 - local addition already has the same.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo frotz >frotz &&
+ git-update-index --add frotz &&
+ echo frotz frotz >frotz &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >7.out &&
+ diff -u M.out 7.out &&
+ check_cache_at frotz dirty'
+
+test_expect_success \
+ '8 - conflicting addition.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo frotz frotz >frotz &&
+ git-update-index --add frotz &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '9 - conflicting addition.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo frotz frotz >frotz &&
+ git-update-index --add frotz &&
+ echo frotz >frotz &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '10 - path removed.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo rezrov >rezrov &&
+ git-update-index --add rezrov &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >10.out &&
+ diff -u M.out 10.out'
+
+test_expect_success \
+ '11 - dirty path removed.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo rezrov >rezrov &&
+ git-update-index --add rezrov &&
+ echo rezrov rezrov >rezrov &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '12 - unmatching local changes being removed.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo rezrov rezrov >rezrov &&
+ git-update-index --add rezrov &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '13 - unmatching local changes being removed.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo rezrov rezrov >rezrov &&
+ git-update-index --add rezrov &&
+ echo rezrov >rezrov &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+cat >expected <<EOF
+-100644 X 0 nitfol
++100644 X 0 nitfol
+EOF
+
+test_expect_success \
+ '14 - unchanged in two heads.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo nitfol nitfol >nitfol &&
+ git-update-index --add nitfol &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >14.out || return 1
+ diff -u M.out 14.out >14diff.out
+ compare_change 14diff.out expected &&
+ check_cache_at nitfol clean'
+
+test_expect_success \
+ '15 - unchanged in two heads.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo nitfol nitfol >nitfol &&
+ git-update-index --add nitfol &&
+ echo nitfol nitfol nitfol >nitfol &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >15.out || return 1
+ diff -u M.out 15.out >15diff.out
+ compare_change 15diff.out expected &&
+ check_cache_at nitfol dirty'
+
+test_expect_success \
+ '16 - conflicting local change.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo bozbar bozbar >bozbar &&
+ git-update-index --add bozbar &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '17 - conflicting local change.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ echo bozbar bozbar >bozbar &&
+ git-update-index --add bozbar &&
+ echo bozbar bozbar bozbar >bozbar &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '18 - local change already having a good result.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ cat bozbar-new >bozbar &&
+ git-update-index --add bozbar &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >18.out &&
+ diff -u M.out 18.out &&
+ check_cache_at bozbar clean'
+
+test_expect_success \
+ '19 - local change already having a good result, further modified.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ cat bozbar-new >bozbar &&
+ git-update-index --add bozbar &&
+ echo gnusto gnusto >bozbar &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >19.out &&
+ diff -u M.out 19.out &&
+ check_cache_at bozbar dirty'
+
+test_expect_success \
+ '20 - no local change, use new tree.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ cat bozbar-old >bozbar &&
+ git-update-index --add bozbar &&
+ read_tree_twoway $treeH $treeM &&
+ git-ls-files --stage >20.out &&
+ diff -u M.out 20.out &&
+ check_cache_at bozbar dirty'
+
+test_expect_success \
+ '21 - no local change, dirty cache.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ cat bozbar-old >bozbar &&
+ git-update-index --add bozbar &&
+ echo gnusto gnusto >bozbar &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+# This fails with straight two-way fast forward.
+test_expect_success \
+ '22 - local change cache updated.' \
+ 'rm -f .git/index &&
+ git-read-tree $treeH &&
+ git-checkout-index -u -f -q -a &&
+ sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
+ git-update-index --add bozbar &&
+ if read_tree_twoway $treeH $treeM; then false; else :; fi'
+
+# Also make sure we did not break DF vs DF/DF case.
+test_expect_success \
+ 'DF vs DF/DF case setup.' \
+ 'rm -f .git/index &&
+ echo DF >DF &&
+ git-update-index --add DF &&
+ treeDF=`git-write-tree` &&
+ echo treeDF $treeDF &&
+ git-ls-tree $treeDF &&
+
+ rm -f DF &&
+ mkdir DF &&
+ echo DF/DF >DF/DF &&
+ git-update-index --add --remove DF DF/DF &&
+ treeDFDF=`git-write-tree` &&
+ echo treeDFDF $treeDFDF &&
+ git-ls-tree $treeDFDF &&
+ git-ls-files --stage >DFDF.out'
+
+test_expect_success \
+ 'DF vs DF/DF case test.' \
+ 'rm -f .git/index &&
+ rm -fr DF &&
+ echo DF >DF &&
+ git-update-index --add DF &&
+ read_tree_twoway $treeDF $treeDFDF &&
+ git-ls-files --stage >DFDFcheck.out &&
+ diff -u DFDF.out DFDFcheck.out &&
+ check_cache_at DF/DF dirty &&
+ :'
+
+test_done
diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh
new file mode 100755
index 000000000..861ef4c0c
--- /dev/null
+++ b/t/t1002-read-tree-m-u-2way.sh
@@ -0,0 +1,324 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Two way merge with read-tree -m -u $H $M
+
+This is identical to t1001, but uses -u to update the work tree as well.
+
+'
+. ./test-lib.sh
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+compare_change () {
+ sed >current \
+ -e '/^--- /d; /^+++ /d; /^@@ /d;' \
+ -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /' "$1"
+ diff -u expected current
+}
+
+check_cache_at () {
+ clean_if_empty=`git-diff-files "$1"`
+ case "$clean_if_empty" in
+ '') echo "$1: clean" ;;
+ ?*) echo "$1: dirty" ;;
+ esac
+ case "$2,$clean_if_empty" in
+ clean,) : ;;
+ clean,?*) false ;;
+ dirty,) false ;;
+ dirty,?*) : ;;
+ esac
+}
+
+test_expect_success \
+ setup \
+ 'echo frotz >frotz &&
+ echo nitfol >nitfol &&
+ echo bozbar >bozbar &&
+ echo rezrov >rezrov &&
+ echo yomin >yomin &&
+ git-update-index --add nitfol bozbar rezrov &&
+ treeH=`git-write-tree` &&
+ echo treeH $treeH &&
+ git-ls-tree $treeH &&
+
+ echo gnusto >bozbar &&
+ git-update-index --add frotz bozbar --force-remove rezrov &&
+ git-ls-files --stage >M.out &&
+ treeM=`git-write-tree` &&
+ echo treeM $treeM &&
+ git-ls-tree $treeM &&
+ sum bozbar frotz nitfol >M.sum &&
+ git-diff-tree $treeH $treeM'
+
+test_expect_success \
+ '1, 2, 3 - no carry forward' \
+ 'rm -f .git/index &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >1-3.out &&
+ cmp M.out 1-3.out &&
+ sum bozbar frotz nitfol >actual3.sum &&
+ cmp M.sum actual3.sum &&
+ check_cache_at bozbar clean &&
+ check_cache_at frotz clean &&
+ check_cache_at nitfol clean'
+
+echo '+100644 X 0 yomin' >expected
+
+test_expect_success \
+ '4 - carry forward local addition.' \
+ 'rm -f .git/index &&
+ git-update-index --add yomin &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >4.out || return 1
+ diff --unified=0 M.out 4.out >4diff.out
+ compare_change 4diff.out expected &&
+ check_cache_at yomin clean &&
+ sum bozbar frotz nitfol >actual4.sum &&
+ cmp M.sum actual4.sum &&
+ echo yomin >yomin1 &&
+ diff yomin yomin1 &&
+ rm -f yomin1'
+
+test_expect_success \
+ '5 - carry forward local addition.' \
+ 'rm -f .git/index &&
+ echo yomin >yomin &&
+ git-update-index --add yomin &&
+ echo yomin yomin >yomin &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >5.out || return 1
+ diff --unified=0 M.out 5.out >5diff.out
+ compare_change 5diff.out expected &&
+ check_cache_at yomin dirty &&
+ sum bozbar frotz nitfol >actual5.sum &&
+ cmp M.sum actual5.sum &&
+ : dirty index should have prevented -u from checking it out. &&
+ echo yomin yomin >yomin1 &&
+ diff yomin yomin1 &&
+ rm -f yomin1'
+
+test_expect_success \
+ '6 - local addition already has the same.' \
+ 'rm -f .git/index &&
+ git-update-index --add frotz &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >6.out &&
+ diff --unified=0 M.out 6.out &&
+ check_cache_at frotz clean &&
+ sum bozbar frotz nitfol >actual3.sum &&
+ cmp M.sum actual3.sum &&
+ echo frotz >frotz1 &&
+ diff frotz frotz1 &&
+ rm -f frotz1'
+
+test_expect_success \
+ '7 - local addition already has the same.' \
+ 'rm -f .git/index &&
+ echo frotz >frotz &&
+ git-update-index --add frotz &&
+ echo frotz frotz >frotz &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >7.out &&
+ diff --unified=0 M.out 7.out &&
+ check_cache_at frotz dirty &&
+ sum bozbar frotz nitfol >actual7.sum &&
+ if cmp M.sum actual7.sum; then false; else :; fi &&
+ : dirty index should have prevented -u from checking it out. &&
+ echo frotz frotz >frotz1 &&
+ diff frotz frotz1 &&
+ rm -f frotz1'
+
+test_expect_success \
+ '8 - conflicting addition.' \
+ 'rm -f .git/index &&
+ echo frotz frotz >frotz &&
+ git-update-index --add frotz &&
+ if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '9 - conflicting addition.' \
+ 'rm -f .git/index &&
+ echo frotz frotz >frotz &&
+ git-update-index --add frotz &&
+ echo frotz >frotz &&
+ if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '10 - path removed.' \
+ 'rm -f .git/index &&
+ echo rezrov >rezrov &&
+ git-update-index --add rezrov &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >10.out &&
+ cmp M.out 10.out &&
+ sum bozbar frotz nitfol >actual10.sum &&
+ cmp M.sum actual10.sum'
+
+test_expect_success \
+ '11 - dirty path removed.' \
+ 'rm -f .git/index &&
+ echo rezrov >rezrov &&
+ git-update-index --add rezrov &&
+ echo rezrov rezrov >rezrov &&
+ if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '12 - unmatching local changes being removed.' \
+ 'rm -f .git/index &&
+ echo rezrov rezrov >rezrov &&
+ git-update-index --add rezrov &&
+ if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '13 - unmatching local changes being removed.' \
+ 'rm -f .git/index &&
+ echo rezrov rezrov >rezrov &&
+ git-update-index --add rezrov &&
+ echo rezrov >rezrov &&
+ if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+cat >expected <<EOF
+-100644 X 0 nitfol
++100644 X 0 nitfol
+EOF
+
+test_expect_success \
+ '14 - unchanged in two heads.' \
+ 'rm -f .git/index &&
+ echo nitfol nitfol >nitfol &&
+ git-update-index --add nitfol &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >14.out || return 1
+ diff --unified=0 M.out 14.out >14diff.out
+ compare_change 14diff.out expected &&
+ sum bozbar frotz >actual14.sum &&
+ grep -v nitfol M.sum > expected14.sum &&
+ cmp expected14.sum actual14.sum &&
+ sum bozbar frotz nitfol >actual14a.sum &&
+ if cmp M.sum actual14a.sum; then false; else :; fi &&
+ check_cache_at nitfol clean &&
+ echo nitfol nitfol >nitfol1 &&
+ diff nitfol nitfol1 &&
+ rm -f nitfol1'
+
+test_expect_success \
+ '15 - unchanged in two heads.' \
+ 'rm -f .git/index &&
+ echo nitfol nitfol >nitfol &&
+ git-update-index --add nitfol &&
+ echo nitfol nitfol nitfol >nitfol &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >15.out || return 1
+ diff --unified=0 M.out 15.out >15diff.out
+ compare_change 15diff.out expected &&
+ check_cache_at nitfol dirty &&
+ sum bozbar frotz >actual15.sum &&
+ grep -v nitfol M.sum > expected15.sum &&
+ cmp expected15.sum actual15.sum &&
+ sum bozbar frotz nitfol >actual15a.sum &&
+ if cmp M.sum actual15a.sum; then false; else :; fi &&
+ echo nitfol nitfol nitfol >nitfol1 &&
+ diff nitfol nitfol1 &&
+ rm -f nitfol1'
+
+test_expect_success \
+ '16 - conflicting local change.' \
+ 'rm -f .git/index &&
+ echo bozbar bozbar >bozbar &&
+ git-update-index --add bozbar &&
+ if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '17 - conflicting local change.' \
+ 'rm -f .git/index &&
+ echo bozbar bozbar >bozbar &&
+ git-update-index --add bozbar &&
+ echo bozbar bozbar bozbar >bozbar &&
+ if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+test_expect_success \
+ '18 - local change already having a good result.' \
+ 'rm -f .git/index &&
+ echo gnusto >bozbar &&
+ git-update-index --add bozbar &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >18.out &&
+ diff --unified=0 M.out 18.out &&
+ check_cache_at bozbar clean &&
+ sum bozbar frotz nitfol >actual18.sum &&
+ cmp M.sum actual18.sum'
+
+test_expect_success \
+ '19 - local change already having a good result, further modified.' \
+ 'rm -f .git/index &&
+ echo gnusto >bozbar &&
+ git-update-index --add bozbar &&
+ echo gnusto gnusto >bozbar &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >19.out &&
+ diff --unified=0 M.out 19.out &&
+ check_cache_at bozbar dirty &&
+ sum frotz nitfol >actual19.sum &&
+ grep -v bozbar M.sum > expected19.sum &&
+ cmp expected19.sum actual19.sum &&
+ sum bozbar frotz nitfol >actual19a.sum &&
+ if cmp M.sum actual19a.sum; then false; else :; fi &&
+ echo gnusto gnusto >bozbar1 &&
+ diff bozbar bozbar1 &&
+ rm -f bozbar1'
+
+test_expect_success \
+ '20 - no local change, use new tree.' \
+ 'rm -f .git/index &&
+ echo bozbar >bozbar &&
+ git-update-index --add bozbar &&
+ git-read-tree -m -u $treeH $treeM &&
+ git-ls-files --stage >20.out &&
+ diff --unified=0 M.out 20.out &&
+ check_cache_at bozbar clean &&
+ sum bozbar frotz nitfol >actual20.sum &&
+ cmp M.sum actual20.sum'
+
+test_expect_success \
+ '21 - no local change, dirty cache.' \
+ 'rm -f .git/index &&
+ echo bozbar >bozbar &&
+ git-update-index --add bozbar &&
+ echo gnusto gnusto >bozbar &&
+ if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+
+# Also make sure we did not break DF vs DF/DF case.
+test_expect_success \
+ 'DF vs DF/DF case setup.' \
+ 'rm -f .git/index &&
+ echo DF >DF &&
+ git-update-index --add DF &&
+ treeDF=`git-write-tree` &&
+ echo treeDF $treeDF &&
+ git-ls-tree $treeDF &&
+
+ rm -f DF &&
+ mkdir DF &&
+ echo DF/DF >DF/DF &&
+ git-update-index --add --remove DF DF/DF &&
+ treeDFDF=`git-write-tree` &&
+ echo treeDFDF $treeDFDF &&
+ git-ls-tree $treeDFDF &&
+ git-ls-files --stage >DFDF.out'
+
+test_expect_success \
+ 'DF vs DF/DF case test.' \
+ 'rm -f .git/index &&
+ rm -fr DF &&
+ echo DF >DF &&
+ git-update-index --add DF &&
+ git-read-tree -m -u $treeDF $treeDFDF &&
+ git-ls-files --stage >DFDFcheck.out &&
+ diff --unified=0 DFDF.out DFDFcheck.out &&
+ check_cache_at DF/DF clean'
+
+test_done
diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh
new file mode 100755
index 000000000..19a0ed4d2
--- /dev/null
+++ b/t/t1100-commit-tree-options.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Copyright (C) 2005 Rene Scharfe
+#
+
+test_description='git-commit-tree options test
+
+This test checks that git-commit-tree can create a specific commit
+object by defining all environment variables that it understands.
+'
+
+. ./test-lib.sh
+
+cat >expected <<EOF
+tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+author Author Name <author@email> 1117148400 +0000
+committer Committer Name <committer@email> 1117150200 +0000
+
+comment text
+EOF
+
+test_expect_success \
+ 'test preparation: write empty tree' \
+ 'git-write-tree >treeid'
+
+test_expect_success \
+ 'construct commit' \
+ 'echo comment text |
+ GIT_AUTHOR_NAME="Author Name" \
+ GIT_AUTHOR_EMAIL="author@email" \
+ GIT_AUTHOR_DATE="2005-05-26 23:00" \
+ GIT_COMMITTER_NAME="Committer Name" \
+ GIT_COMMITTER_EMAIL="committer@email" \
+ GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ TZ=GMT git-commit-tree `cat treeid` >commitid 2>/dev/null'
+
+test_expect_success \
+ 'read commit' \
+ 'git-cat-file commit `cat commitid` >commit'
+
+test_expect_success \
+ 'compare commit' \
+ 'diff expected commit'
+
+test_done
diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh
new file mode 100644
index 000000000..d7562e974
--- /dev/null
+++ b/t/t1200-tutorial.sh
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-rev-parse with different parent options'
+
+. ./test-lib.sh
+
+echo "Hello World" > hello
+echo "Silly example" > example
+
+git-update-index --add hello example
+
+test_expect_success 'blob' "test blob = \"$(git-cat-file -t 557db03)\""
+
+test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git-cat-file blob 557db03)\""
+
+echo "It's a new day for git" >>hello
+cat > diff.expect << EOF
+diff --git a/hello b/hello
+index 557db03..263414f 100644
+--- a/hello
++++ b/hello
+@@ -1 +1,2 @@
+ Hello World
++It's a new day for git
+EOF
+git-diff-files -p > diff.output
+test_expect_success 'git-diff-files -p' 'cmp diff.expect diff.output'
+git diff > diff.output
+test_expect_success 'git diff' 'cmp diff.expect diff.output'
+
+tree=$(git-write-tree 2>/dev/null)
+
+test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree"
+
+output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
+
+test_expect_success 'commit' "test 'Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb' = \"$output\""
+
+git-diff-index -p HEAD > diff.output
+test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output'
+
+git diff HEAD > diff.output
+test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
+
+#rm hello
+#test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
+
+cat > whatchanged.expect << EOF
+diff-tree VARIABLE (from root)
+Author: VARIABLE
+Date: VARIABLE
+
+ Initial commit
+
+diff --git a/example b/example
+new file mode 100644
+index 0000000..f24c74a
+--- /dev/null
++++ b/example
+@@ -0,0 +1 @@
++Silly example
+diff --git a/hello b/hello
+new file mode 100644
+index 0000000..557db03
+--- /dev/null
++++ b/hello
+@@ -0,0 +1 @@
++Hello World
+EOF
+
+git-whatchanged -p --root | \
+ sed -e "1s/^\(.\{10\}\).\{40\}/\1VARIABLE/" \
+ -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
+> whatchanged.output
+test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
+
+git tag my-first-tag
+test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
+
+# TODO: test git-clone
+
+git checkout -b mybranch
+test_expect_success 'git checkout -b mybranch' 'cmp .git/refs/heads/master .git/refs/heads/mybranch'
+
+cat > branch.expect <<EOF
+ master
+* mybranch
+EOF
+
+git branch > branch.output
+test_expect_success 'git branch' 'cmp branch.expect branch.output'
+
+git checkout mybranch
+echo "Work, work, work" >>hello
+git commit -m 'Some work.' hello
+
+git checkout master
+
+echo "Play, play, play" >>hello
+echo "Lots of fun" >>example
+git commit -m 'Some fun.' hello example
+
+test_expect_failure 'git resolve now fails' 'git resolve HEAD mybranch "Merge work in mybranch"'
+
+cat > hello << EOF
+Hello World
+It's a new day for git
+Play, play, play
+Work, work, work
+EOF
+
+git commit -m 'Merged "mybranch" changes.' hello
+
+cat > show-branch.expect << EOF
+* [master] Merged "mybranch" changes.
+ ! [mybranch] Some work.
+--
++ [master] Merged "mybranch" changes.
+++ [mybranch] Some work.
+EOF
+
+git show-branch --topo-order master mybranch > show-branch.output
+test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output'
+
+git checkout mybranch
+
+cat > resolve.expect << EOF
+Updating from VARIABLE to VARIABLE.
+ example | 1 +
+ hello | 1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+EOF
+
+git resolve HEAD master "Merge upstream changes." | \
+ sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" > resolve.output
+test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
+
+cat > show-branch2.expect << EOF
+! [master] Merged "mybranch" changes.
+ * [mybranch] Merged "mybranch" changes.
+--
+++ [master] Merged "mybranch" changes.
+EOF
+
+git show-branch --topo-order master mybranch > show-branch2.output
+test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.output'
+
+# TODO: test git fetch
+
+# TODO: test git push
+
+test_expect_success 'git repack' 'git repack'
+test_expect_success 'git prune-packed' 'git prune-packed'
+test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
+
+test_done
+
diff --git a/t/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh
new file mode 100755
index 000000000..03ea4dece
--- /dev/null
+++ b/t/t2000-checkout-cache-clash.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-index test.
+
+This test registers the following filesystem structure in the
+cache:
+
+ path0 - a file
+ path1/file1 - a file in a directory
+
+And then tries to checkout in a work tree that has the following:
+
+ path0/file0 - a file in a directory
+ path1 - a file
+
+The git-checkout-index command should fail when attempting to checkout
+path0, finding it is occupied by a directory, and path1/file1, finding
+path1 is occupied by a non-directory. With "-f" flag, it should remove
+the conflicting paths and succeed.
+'
+. ./test-lib.sh
+
+date >path0
+mkdir path1
+date >path1/file1
+
+test_expect_success \
+ 'git-update-index --add various paths.' \
+ 'git-update-index --add path0 path1/file1'
+
+rm -fr path0 path1
+mkdir path0
+date >path0/file0
+date >path1
+
+test_expect_failure \
+ 'git-checkout-index without -f should fail on conflicting work tree.' \
+ 'git-checkout-index -a'
+
+test_expect_success \
+ 'git-checkout-index with -f should succeed.' \
+ 'git-checkout-index -f -a'
+
+test_expect_success \
+ 'git-checkout-index conflicting paths.' \
+ 'test -f path0 && test -d path1 && test -f path1/file1'
+
+test_done
+
+
diff --git a/t/t2001-checkout-cache-clash.sh b/t/t2001-checkout-cache-clash.sh
new file mode 100755
index 000000000..b1c5263a9
--- /dev/null
+++ b/t/t2001-checkout-cache-clash.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-index test.
+
+This test registers the following filesystem structure in the cache:
+
+ path0/file0 - a file in a directory
+ path1/file1 - a file in a directory
+
+and attempts to check it out when the work tree has:
+
+ path0/file0 - a file in a directory
+ path1 - a symlink pointing at "path0"
+
+Checkout cache should fail to extract path1/file1 because the leading
+path path1 is occupied by a non-directory. With "-f" it should remove
+the symlink path1 and create directory path1 and file path1/file1.
+'
+. ./test-lib.sh
+
+show_files() {
+ # show filesystem files, just [-dl] for type and name
+ find path? -ls |
+ sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /'
+ # what's in the cache, just mode and name
+ git-ls-files --stage |
+ sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /'
+ # what's in the tree, just mode and name.
+ git-ls-tree -r "$1" |
+ sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /'
+}
+
+mkdir path0
+date >path0/file0
+test_expect_success \
+ 'git-update-index --add path0/file0' \
+ 'git-update-index --add path0/file0'
+test_expect_success \
+ 'writing tree out with git-write-tree' \
+ 'tree1=$(git-write-tree)'
+test_debug 'show_files $tree1'
+
+mkdir path1
+date >path1/file1
+test_expect_success \
+ 'git-update-index --add path1/file1' \
+ 'git-update-index --add path1/file1'
+test_expect_success \
+ 'writing tree out with git-write-tree' \
+ 'tree2=$(git-write-tree)'
+test_debug 'show_files $tree2'
+
+rm -fr path1
+test_expect_success \
+ 'read previously written tree and checkout.' \
+ 'git-read-tree -m $tree1 && git-checkout-index -f -a'
+test_debug 'show_files $tree1'
+
+ln -s path0 path1
+test_expect_success \
+ 'git-update-index --add a symlink.' \
+ 'git-update-index --add path1'
+test_expect_success \
+ 'writing tree out with git-write-tree' \
+ 'tree3=$(git-write-tree)'
+test_debug 'show_files $tree3'
+
+# Morten says "Got that?" here.
+# Test begins.
+
+test_expect_success \
+ 'read previously written tree and checkout.' \
+ 'git-read-tree $tree2 && git-checkout-index -f -a'
+test_debug show_files $tree2
+
+test_expect_success \
+ 'checking out conflicting path with -f' \
+ 'test ! -h path0 && test -d path0 &&
+ test ! -h path1 && test -d path1 &&
+ test ! -h path0/file0 && test -f path0/file0 &&
+ test ! -h path1/file1 && test -f path1/file1'
+
+test_done
+
diff --git a/t/t2002-checkout-cache-u.sh b/t/t2002-checkout-cache-u.sh
new file mode 100755
index 000000000..4352ddb1c
--- /dev/null
+++ b/t/t2002-checkout-cache-u.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-index -u test.
+
+With -u flag, git-checkout-index internally runs the equivalent of
+git-update-index --refresh on the checked out entry.'
+
+. ./test-lib.sh
+
+test_expect_success \
+'preparation' '
+echo frotz >path0 &&
+git-update-index --add path0 &&
+t=$(git-write-tree)'
+
+test_expect_failure \
+'without -u, git-checkout-index smudges stat information.' '
+rm -f path0 &&
+git-read-tree $t &&
+git-checkout-index -f -a &&
+git-diff-files | diff - /dev/null'
+
+test_expect_success \
+'with -u, git-checkout-index picks up stat information from new files.' '
+rm -f path0 &&
+git-read-tree $t &&
+git-checkout-index -u -f -a &&
+git-diff-files | diff - /dev/null'
+
+test_done
diff --git a/t/t2003-checkout-cache-mkdir.sh b/t/t2003-checkout-cache-mkdir.sh
new file mode 100755
index 000000000..f9bc90aee
--- /dev/null
+++ b/t/t2003-checkout-cache-mkdir.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-checkout-index --prefix test.
+
+This test makes sure that --prefix option works as advertised, and
+also verifies that such leading path may contain symlinks, unlike
+the GIT controlled paths.
+'
+
+. ./test-lib.sh
+
+test_expect_success \
+ 'setup' \
+ 'mkdir path1 &&
+ echo frotz >path0 &&
+ echo rezrov >path1/file1 &&
+ git-update-index --add path0 path1/file1'
+
+test_expect_success \
+ 'have symlink in place where dir is expected.' \
+ 'rm -fr path0 path1 &&
+ mkdir path2 &&
+ ln -s path2 path1 &&
+ git-checkout-index -f -a &&
+ test ! -h path1 && test -d path1 &&
+ test -f path1/file1 && test ! -f path2/file1'
+
+test_expect_success \
+ 'use --prefix=path2/' \
+ 'rm -fr path0 path1 path2 &&
+ mkdir path2 &&
+ git-checkout-index --prefix=path2/ -f -a &&
+ test -f path2/path0 &&
+ test -f path2/path1/file1 &&
+ test ! -f path0 &&
+ test ! -f path1/file1'
+
+test_expect_success \
+ 'use --prefix=tmp-' \
+ 'rm -fr path0 path1 path2 tmp* &&
+ git-checkout-index --prefix=tmp- -f -a &&
+ test -f tmp-path0 &&
+ test -f tmp-path1/file1 &&
+ test ! -f path0 &&
+ test ! -f path1/file1'
+
+test_expect_success \
+ 'use --prefix=tmp- but with a conflicting file and dir' \
+ 'rm -fr path0 path1 path2 tmp* &&
+ echo nitfol >tmp-path1 &&
+ mkdir tmp-path0 &&
+ git-checkout-index --prefix=tmp- -f -a &&
+ test -f tmp-path0 &&
+ test -f tmp-path1/file1 &&
+ test ! -f path0 &&
+ test ! -f path1/file1'
+
+# Linus fix #1
+test_expect_success \
+ 'use --prefix=tmp/orary/ where tmp is a symlink' \
+ 'rm -fr path0 path1 path2 tmp* &&
+ mkdir tmp1 tmp1/orary &&
+ ln -s tmp1 tmp &&
+ git-checkout-index --prefix=tmp/orary/ -f -a &&
+ test -d tmp1/orary &&
+ test -f tmp1/orary/path0 &&
+ test -f tmp1/orary/path1/file1 &&
+ test -h tmp'
+
+# Linus fix #2
+test_expect_success \
+ 'use --prefix=tmp/orary- where tmp is a symlink' \
+ 'rm -fr path0 path1 path2 tmp* &&
+ mkdir tmp1 &&
+ ln -s tmp1 tmp &&
+ git-checkout-index --prefix=tmp/orary- -f -a &&
+ test -f tmp1/orary-path0 &&
+ test -f tmp1/orary-path1/file1 &&
+ test -h tmp'
+
+# Linus fix #3
+test_expect_success \
+ 'use --prefix=tmp- where tmp-path1 is a symlink' \
+ 'rm -fr path0 path1 path2 tmp* &&
+ mkdir tmp1 &&
+ ln -s tmp1 tmp-path1 &&
+ git-checkout-index --prefix=tmp- -f -a &&
+ test -f tmp-path0 &&
+ test ! -h tmp-path1 &&
+ test -d tmp-path1 &&
+ test -f tmp-path1/file1'
+
+test_done
diff --git a/t/t2100-update-cache-badpath.sh b/t/t2100-update-cache-badpath.sh
new file mode 100755
index 000000000..5bc0a3bed
--- /dev/null
+++ b/t/t2100-update-cache-badpath.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-update-index nonsense-path test.
+
+This test creates the following structure in the cache:
+
+ path0 - a file
+ path1 - a symlink
+ path2/file2 - a file in a directory
+ path3/file3 - a file in a directory
+
+and tries to git-update-index --add the following:
+
+ path0/file0 - a file in a directory
+ path1/file1 - a file in a directory
+ path2 - a file
+ path3 - a symlink
+
+All of the attempts should fail.
+'
+
+. ./test-lib.sh
+
+mkdir path2 path3
+date >path0
+ln -s xyzzy path1
+date >path2/file2
+date >path3/file3
+
+test_expect_success \
+ 'git-update-index --add to add various paths.' \
+ 'git-update-index --add -- path0 path1 path2/file2 path3/file3'
+
+rm -fr path?
+
+mkdir path0 path1
+date >path2
+ln -s frotz path3
+date >path0/file0
+date >path1/file1
+
+for p in path0/file0 path1/file1 path2 path3
+do
+ test_expect_failure \
+ "git-update-index to add conflicting path $p should fail." \
+ "git-update-index --add -- $p"
+done
+test_done
diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh
new file mode 100755
index 000000000..1f461e3e8
--- /dev/null
+++ b/t/t3000-ls-files-others.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files test (--others should pick up symlinks).
+
+This test runs git-ls-files --others with the following on the
+filesystem.
+
+ path0 - a file
+ path1 - a symlink
+ path2/file2 - a file in a directory
+'
+. ./test-lib.sh
+
+date >path0
+ln -s xyzzy path1
+mkdir path2
+date >path2/file2
+test_expect_success \
+ 'git-ls-files --others to show output.' \
+ 'git-ls-files --others >output'
+cat >expected <<EOF
+output
+path0
+path1
+path2/file2
+EOF
+
+test_expect_success \
+ 'git-ls-files --others should pick up symlinks.' \
+ 'diff output expected'
+test_done
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
new file mode 100755
index 000000000..fde2bb25f
--- /dev/null
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files --others --exclude
+
+This test runs git-ls-files --others and tests --exclude patterns.
+'
+
+. ./test-lib.sh
+
+rm -fr one three
+for dir in . one one/two three
+do
+ mkdir -p $dir &&
+ for i in 1 2 3 4 5 6 7 8
+ do
+ >$dir/a.$i
+ done
+done
+
+cat >expect <<EOF
+a.2
+a.4
+a.5
+a.8
+one/a.3
+one/a.4
+one/a.5
+one/a.7
+one/two/a.2
+one/two/a.3
+one/two/a.5
+one/two/a.7
+one/two/a.8
+three/a.2
+three/a.3
+three/a.4
+three/a.5
+three/a.8
+EOF
+
+echo '.gitignore
+output
+expect
+.gitignore
+*.7
+!*.8' >.git/ignore
+
+echo '*.1
+/*.3
+!*.6' >.gitignore
+echo '*.2
+two/*.4
+!*.7
+*.8' >one/.gitignore
+echo '!*.2
+!*.8' >one/two/.gitignore
+
+test_expect_success \
+ 'git-ls-files --others with various exclude options.' \
+ 'git-ls-files --others \
+ --exclude=\*.6 \
+ --exclude-per-directory=.gitignore \
+ --exclude-from=.git/ignore \
+ >output &&
+ diff -u expect output'
+
+# Test \r\n (MSDOS-like systems)
+echo -ne '*.1\r\n/*.3\r\n!*.6\r\n' >.gitignore
+
+test_expect_success \
+ 'git-ls-files --others with \r\n line endings.' \
+ 'git-ls-files --others \
+ --exclude=\*.6 \
+ --exclude-per-directory=.gitignore \
+ --exclude-from=.git/ignore \
+ >output &&
+ diff -u expect output'
+
+test_done
diff --git a/t/t3002-ls-files-dashpath.sh b/t/t3002-ls-files-dashpath.sh
new file mode 100755
index 000000000..b42f1382b
--- /dev/null
+++ b/t/t3002-ls-files-dashpath.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files test (-- to terminate the path list).
+
+This test runs git-ls-files --others with the following on the
+filesystem.
+
+ path0 - a file
+ -foo - a file with a funny name.
+ -- - another file with a funny name.
+'
+. ./test-lib.sh
+
+test_expect_success \
+ setup \
+ 'echo frotz >path0 &&
+ echo frotz >./-foo &&
+ echo frotz >./--'
+
+test_expect_success \
+ 'git-ls-files without path restriction.' \
+ 'git-ls-files --others >output &&
+ diff -u output - <<EOF
+--
+-foo
+output
+path0
+EOF
+'
+
+test_expect_success \
+ 'git-ls-files with path restriction.' \
+ 'git-ls-files --others path0 >output &&
+ diff -u output - <<EOF
+path0
+EOF
+'
+
+test_expect_success \
+ 'git-ls-files with path restriction with --.' \
+ 'git-ls-files --others -- path0 >output &&
+ diff -u output - <<EOF
+path0
+EOF
+'
+
+test_expect_success \
+ 'git-ls-files with path restriction with -- --.' \
+ 'git-ls-files --others -- -- >output &&
+ diff -u output - <<EOF
+--
+EOF
+'
+
+test_expect_success \
+ 'git-ls-files with no path restriction.' \
+ 'git-ls-files --others -- >output &&
+ diff -u output - <<EOF
+--
+-foo
+output
+path0
+EOF
+'
+
+test_done
diff --git a/t/t3010-ls-files-killed-modified.sh b/t/t3010-ls-files-killed-modified.sh
new file mode 100755
index 000000000..5fc197671
--- /dev/null
+++ b/t/t3010-ls-files-killed-modified.sh
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-files -k and -m flags test.
+
+This test prepares the following in the cache:
+
+ path0 - a file
+ path1 - a symlink
+ path2/file2 - a file in a directory
+ path3/file3 - a file in a directory
+
+and the following on the filesystem:
+
+ path0/file0 - a file in a directory
+ path1/file1 - a file in a directory
+ path2 - a file
+ path3 - a symlink
+ path4 - a file
+ path5 - a symlink
+ path6/file6 - a file in a directory
+
+git-ls-files -k should report that existing filesystem
+objects except path4, path5 and path6/file6 to be killed.
+
+Also for modification test, the cache and working tree have:
+
+ path7 - an empty file, modified to a non-empty file.
+ path8 - a non-empty file, modified to an empty file.
+ path9 - an empty file, cache dirtied.
+ 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.
+'
+. ./test-lib.sh
+
+date >path0
+ln -s xyzzy path1
+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
+ln -s frotz path3
+ln -s nitfol path5
+mkdir path0 path1 path6
+date >path0/file0
+date >path1/file1
+date >path6/file6
+date >path7
+: >path8
+: >path9
+touch path10
+
+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 \
+ 'validate git-ls-files -k output.' \
+ 'diff .output .expected'
+
+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 \
+ 'validate git-ls-files -m output.' \
+ 'diff .output .expected'
+
+test_done
diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
new file mode 100755
index 000000000..c6ce56c86
--- /dev/null
+++ b/t/t3100-ls-tree-restrict.sh
@@ -0,0 +1,131 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-ls-tree test.
+
+This test runs git-ls-tree with the following in a tree.
+
+ path0 - a file
+ path1 - a symlink
+ path2/foo - a file in a directory
+ path2/bazbo - a symlink in a directory
+ path2/baz/b - a file in a directory in a directory
+
+The new path restriction code should do the right thing for path2 and
+path2/baz. Also path0/ should snow nothing.
+'
+. ./test-lib.sh
+
+test_expect_success \
+ 'setup' \
+ 'mkdir path2 path2/baz &&
+ echo Hi >path0 &&
+ ln -s path0 path1 &&
+ echo Lo >path2/foo &&
+ ln -s ../path1 path2/bazbo &&
+ echo Mi >path2/baz/b &&
+ find path? \( -type f -o -type l \) -print |
+ xargs git-update-index --add &&
+ tree=`git-write-tree` &&
+ echo $tree'
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+test_output () {
+ sed -e "s/ $_x40 / X /" <current >check
+ diff -u expected check
+}
+
+test_expect_success \
+ 'ls-tree plain' \
+ 'git-ls-tree $tree >current &&
+ cat >expected <<\EOF &&
+100644 blob X path0
+120000 blob X path1
+040000 tree X path2
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree recursive' \
+ 'git-ls-tree -r $tree >current &&
+ cat >expected <<\EOF &&
+100644 blob X path0
+120000 blob X path1
+040000 tree X path2
+040000 tree X path2/baz
+100644 blob X path2/baz/b
+120000 blob X path2/bazbo
+100644 blob X path2/foo
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filtered with path' \
+ 'git-ls-tree $tree path >current &&
+ cat >expected <<\EOF &&
+EOF
+ test_output'
+
+
+test_expect_success \
+ 'ls-tree filtered with path1 path0' \
+ 'git-ls-tree $tree path1 path0 >current &&
+ cat >expected <<\EOF &&
+120000 blob X path1
+100644 blob X path0
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filtered with path0/' \
+ 'git-ls-tree $tree path0/ >current &&
+ cat >expected <<\EOF &&
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filtered with path2' \
+ 'git-ls-tree $tree path2 >current &&
+ cat >expected <<\EOF &&
+040000 tree X path2
+040000 tree X path2/baz
+120000 blob X path2/bazbo
+100644 blob X path2/foo
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filtered with path2/baz' \
+ 'git-ls-tree $tree path2/baz >current &&
+ cat >expected <<\EOF &&
+040000 tree X path2/baz
+100644 blob X path2/baz/b
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filtered with path2' \
+ 'git-ls-tree $tree path2 >current &&
+ cat >expected <<\EOF &&
+040000 tree X path2
+040000 tree X path2/baz
+120000 blob X path2/bazbo
+100644 blob X path2/foo
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filtered with path2/' \
+ 'git-ls-tree $tree path2/ >current &&
+ cat >expected <<\EOF &&
+040000 tree X path2
+040000 tree X path2/baz
+120000 blob X path2/bazbo
+100644 blob X path2/foo
+EOF
+ test_output'
+
+test_done
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
new file mode 100644
index 000000000..541036834
--- /dev/null
+++ b/t/t3101-ls-tree-dirname.sh
@@ -0,0 +1,160 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2005 Robert Fitzsimons
+#
+
+test_description='git-ls-tree directory and filenames handling.
+
+This test runs git-ls-tree with the following in a tree.
+
+ 1.txt - a file
+ 2.txt - a file
+ path0/a/b/c/1.txt - a file in a directory
+ path1/b/c/1.txt - a file in a directory
+ path2/1.txt - a file in a directory
+ path3/1.txt - a file in a directory
+ path3/2.txt - a file in a directory
+
+Test the handling of mulitple directories which have matching file
+entries. Also test odd filename and missing entries handling.
+'
+. ./test-lib.sh
+
+test_expect_success \
+ 'setup' \
+ 'echo 111 >1.txt &&
+ echo 222 >2.txt &&
+ mkdir path0 path0/a path0/a/b path0/a/b/c &&
+ echo 111 >path0/a/b/c/1.txt &&
+ mkdir path1 path1/b path1/b/c &&
+ echo 111 >path1/b/c/1.txt &&
+ mkdir path2 &&
+ echo 111 >path2/1.txt &&
+ mkdir path3 &&
+ echo 111 >path3/1.txt &&
+ echo 222 >path3/2.txt &&
+ find *.txt path* \( -type f -o -type l \) -print |
+ xargs git-update-index --add &&
+ tree=`git-write-tree` &&
+ echo $tree'
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+test_output () {
+ sed -e "s/ $_x40 / X /" <current >check
+ diff -u expected check
+}
+
+test_expect_success \
+ 'ls-tree plain' \
+ 'git-ls-tree $tree >current &&
+ cat >expected <<\EOF &&
+100644 blob X 1.txt
+100644 blob X 2.txt
+040000 tree X path0
+040000 tree X path1
+040000 tree X path2
+040000 tree X path3
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree recursive' \
+ 'git-ls-tree -r $tree >current &&
+ cat >expected <<\EOF &&
+100644 blob X 1.txt
+100644 blob X 2.txt
+040000 tree X path0
+040000 tree X path0/a
+040000 tree X path0/a/b
+040000 tree X path0/a/b/c
+100644 blob X path0/a/b/c/1.txt
+040000 tree X path1
+040000 tree X path1/b
+040000 tree X path1/b/c
+100644 blob X path1/b/c/1.txt
+040000 tree X path2
+100644 blob X path2/1.txt
+040000 tree X path3
+100644 blob X path3/1.txt
+100644 blob X path3/2.txt
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filter 1.txt' \
+ 'git-ls-tree $tree 1.txt >current &&
+ cat >expected <<\EOF &&
+100644 blob X 1.txt
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filter path1/b/c/1.txt' \
+ 'git-ls-tree $tree path1/b/c/1.txt >current &&
+ cat >expected <<\EOF &&
+100644 blob X path1/b/c/1.txt
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filter all 1.txt files' \
+ 'git-ls-tree $tree 1.txt path0/a/b/c/1.txt path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
+ cat >expected <<\EOF &&
+100644 blob X 1.txt
+100644 blob X path0/a/b/c/1.txt
+100644 blob X path1/b/c/1.txt
+100644 blob X path2/1.txt
+100644 blob X path3/1.txt
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filter directories' \
+ 'git-ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
+ cat >expected <<\EOF &&
+040000 tree X path3
+100644 blob X path3/1.txt
+100644 blob X path3/2.txt
+040000 tree X path2
+100644 blob X path2/1.txt
+040000 tree X path0/a/b/c
+100644 blob X path0/a/b/c/1.txt
+040000 tree X path1/b/c
+100644 blob X path1/b/c/1.txt
+040000 tree X path0/a
+040000 tree X path0/a/b
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filter odd names' \
+ 'git-ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&
+ cat >expected <<\EOF &&
+100644 blob X 1.txt
+100644 blob X 1.txt
+100644 blob X 1.txt
+100644 blob X path3/1.txt
+100644 blob X path3/1.txt
+100644 blob X path3/1.txt
+040000 tree X path3
+100644 blob X path3/1.txt
+100644 blob X path3/2.txt
+040000 tree X path3
+100644 blob X path3/1.txt
+100644 blob X path3/2.txt
+040000 tree X path3
+100644 blob X path3/1.txt
+100644 blob X path3/2.txt
+EOF
+ test_output'
+
+test_expect_success \
+ 'ls-tree filter missing files and extra slashes' \
+ 'git-ls-tree $tree 1.txt/ abc.txt path3//23.txt path3/2.txt/// >current &&
+ cat >expected <<\EOF &&
+EOF
+ test_output'
+
+test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
new file mode 100755
index 000000000..36f7749be
--- /dev/null
+++ b/t/t3200-branch.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Amos Waterland
+#
+
+test_description='git branch --foo should not create bogus branch
+
+This test runs git branch --help and checks that the argument is properly
+handled. Specifically, that a bogus branch is not created.
+'
+. ./test-lib.sh
+
+test_expect_success \
+ 'prepare an trivial repository' \
+ 'echo Hello > A &&
+ git-update-index --add A &&
+ git-commit -m "Initial commit."'
+
+test_expect_failure \
+ 'git branch --help should return error code' \
+ 'git-branch --help'
+
+test_expect_failure \
+ 'git branch --help should not have created a bogus branch' \
+ 'test -f .git/refs/heads/--help'
+
+test_expect_success \
+ 'git branch abc should create a branch' \
+ 'git-branch abc && test -f .git/refs/heads/abc'
+
+test_expect_success \
+ 'git branch a/b/c should create a branch' \
+ 'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
+
+test_done
diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh
new file mode 100755
index 000000000..897c37842
--- /dev/null
+++ b/t/t3300-funny-names.sh
@@ -0,0 +1,142 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Pathnames with funny characters.
+
+This test tries pathnames with funny characters in the working
+tree, index, and tree objects.
+'
+
+# since FAT/NTFS does not allow tabs in filenames, skip this test
+test "$(uname -o 2>/dev/null)" = Cygwin && exit 0
+
+. ./test-lib.sh
+
+p0='no-funny'
+p1='tabs and spaces'
+p2='just space'
+
+cat >"$p0" <<\EOF
+1. A quick brown fox jumps over the lazy cat, oops dog.
+2. A quick brown fox jumps over the lazy cat, oops dog.
+3. A quick brown fox jumps over the lazy cat, oops dog.
+EOF
+
+cat >"$p1" "$p0"
+echo 'Foo Bar Baz' >"$p2"
+
+echo 'just space
+no-funny' >expected
+test_expect_success 'git-ls-files no-funny' \
+ 'git-update-index --add "$p0" "$p2" &&
+ git-ls-files >current &&
+ diff -u expected current'
+
+t0=`git-write-tree`
+echo "$t0" >t0
+
+echo 'just space
+no-funny
+"tabs\tand spaces"' >expected
+test_expect_success 'git-ls-files with-funny' \
+ 'git-update-index --add "$p1" &&
+ git-ls-files >current &&
+ diff -u expected current'
+
+echo 'just space
+no-funny
+tabs and spaces' >expected
+test_expect_success 'git-ls-files -z with-funny' \
+ 'git-ls-files -z | tr \\0 \\012 >current &&
+ diff -u expected current'
+
+t1=`git-write-tree`
+echo "$t1" >t1
+
+echo 'just space
+no-funny
+"tabs\tand spaces"' >expected
+test_expect_success 'git-ls-tree with funny' \
+ 'git-ls-tree -r $t1 | sed -e "s/^[^ ]* //" >current &&
+ diff -u expected current'
+
+echo 'A "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-index with-funny' \
+ 'git-diff-index --name-status $t0 >current &&
+ diff -u expected current'
+
+test_expect_success 'git-diff-tree with-funny' \
+ 'git-diff-tree --name-status $t0 $t1 >current &&
+ diff -u expected current'
+
+echo 'A
+tabs and spaces' >expected
+test_expect_success 'git-diff-index -z with-funny' \
+ 'git-diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
+ diff -u expected current'
+
+test_expect_success 'git-diff-tree -z with-funny' \
+ 'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
+ diff -u expected current'
+
+echo 'CNUM no-funny "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-tree -C with-funny' \
+ 'git-diff-tree -C --find-copies-harder --name-status \
+ $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
+ diff -u expected current'
+
+echo 'RNUM no-funny "tabs\tand spaces"' >expected
+test_expect_success 'git-diff-tree delete with-funny' \
+ 'git-update-index --force-remove "$p0" &&
+ git-diff-index -M --name-status \
+ $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
+ diff -u expected current'
+
+echo 'diff --git a/no-funny "b/tabs\tand spaces"
+similarity index NUM%
+rename from no-funny
+rename to "tabs\tand spaces"' >expected
+
+test_expect_success 'git-diff-tree delete with-funny' \
+ 'git-diff-index -M -p $t0 |
+ sed -e "s/index [0-9]*%/index NUM%/" >current &&
+ diff -u expected current'
+
+chmod +x "$p1"
+echo 'diff --git a/no-funny "b/tabs\tand spaces"
+old mode 100644
+new mode 100755
+similarity index NUM%
+rename from no-funny
+rename to "tabs\tand spaces"' >expected
+
+test_expect_success 'git-diff-tree delete with-funny' \
+ 'git-diff-index -M -p $t0 |
+ sed -e "s/index [0-9]*%/index NUM%/" >current &&
+ diff -u expected current'
+
+echo >expected ' "tabs\tand spaces"
+ 1 files changed, 0 insertions(+), 0 deletions(-)'
+test_expect_success 'git-diff-tree rename with-funny applied' \
+ 'git-diff-index -M -p $t0 |
+ git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+ diff -u expected current'
+
+echo >expected ' no-funny
+ "tabs\tand spaces"
+ 2 files changed, 3 insertions(+), 3 deletions(-)'
+
+test_expect_success 'git-diff-tree delete with-funny applied' \
+ 'git-diff-index -p $t0 |
+ git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+ diff -u expected current'
+
+test_expect_success 'git-apply non-git diff' \
+ 'git-diff-index -p $t0 |
+ sed -ne "/^[-+@]/p" |
+ git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+ diff -u expected current'
+
+test_done
diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh
new file mode 100755
index 000000000..beb6d8f48
--- /dev/null
+++ b/t/t4000-diff-format.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test built-in diff output engine.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+echo >path0 'Line 1
+Line 2
+line 3'
+cat path0 >path1
+chmod +x path1
+
+test_expect_success \
+ 'update-cache --add two files with and without +x.' \
+ 'git-update-index --add path0 path1'
+
+mv path0 path0-
+sed -e 's/line/Line/' <path0- >path0
+chmod +x path0
+rm -f path1
+test_expect_success \
+ 'git-diff-files -p after editing work tree.' \
+ 'git-diff-files -p >current'
+cat >expected <<\EOF
+diff --git a/path0 b/path0
+old mode 100644
+new mode 100755
+--- a/path0
++++ b/path0
+@@ -1,3 +1,3 @@
+ Line 1
+ Line 2
+-line 3
++Line 3
+diff --git a/path1 b/path1
+deleted file mode 100755
+--- a/path1
++++ /dev/null
+@@ -1,3 +0,0 @@
+-Line 1
+-Line 2
+-line 3
+EOF
+
+test_expect_success \
+ 'validate git-diff-files -p output.' \
+ 'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
new file mode 100755
index 000000000..2e3c20d6b
--- /dev/null
+++ b/t/t4001-diff-rename.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test rename detection in diff engine.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+echo >path0 'Line 1
+Line 2
+Line 3
+Line 4
+Line 5
+Line 6
+Line 7
+Line 8
+Line 9
+Line 10
+line 11
+Line 12
+Line 13
+Line 14
+Line 15
+'
+
+test_expect_success \
+ 'update-cache --add a file.' \
+ 'git-update-index --add path0'
+
+test_expect_success \
+ 'write that tree.' \
+ 'tree=$(git-write-tree) && echo $tree'
+
+sed -e 's/line/Line/' <path0 >path1
+rm -f path0
+test_expect_success \
+ 'renamed and edited the file.' \
+ 'git-update-index --add --remove path0 path1'
+
+test_expect_success \
+ 'git-diff-index -p -M after rename and editing.' \
+ 'git-diff-index -p -M $tree >current'
+cat >expected <<\EOF
+diff --git a/path0 b/path1
+rename from path0
+rename to path1
+--- a/path0
++++ b/path1
+@@ -8,7 +8,7 @@ Line 7
+ Line 8
+ Line 9
+ Line 10
+-line 11
++Line 11
+ Line 12
+ Line 13
+ Line 14
+EOF
+
+test_expect_success \
+ 'validate the output.' \
+ 'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh
new file mode 100755
index 000000000..769274aa5
--- /dev/null
+++ b/t/t4002-diff-basic.sh
@@ -0,0 +1,247 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test diff raw-output.
+
+'
+. ./test-lib.sh
+. ../lib-read-tree-m-3way.sh
+
+cat >.test-plain-OA <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A AA
+:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 A AN
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D DD
+:000000 040000 0000000000000000000000000000000000000000 6d50f65d3bdab91c63444294d38f08aeff328e42 A DF
+:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D DM
+:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D DN
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A LL
+:100644 100644 03f24c8c4700babccfd28b654e7e8eac402ad6cd 103d9f89b50b9aad03054b579be5e7aa665f2d57 M MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 M MM
+:100644 100644 bd084b0c27c7b6cc34f11d6d0509a29be3caf970 a716d58de4a570e0038f5c307bd8db34daea021f M MN
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 4c86f9a85fbc5e6804ee2e17a797538fbe785bca M TT
+:040000 040000 7d670fdcdb9929f6c7dac196ff78689cd1c566a1 5e5f22072bb39f6e12cf663a57cb634c76eefb49 M Z
+EOF
+
+cat >.test-recursive-OA <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A AA
+:000000 100644 0000000000000000000000000000000000000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 A AN
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D DD
+:000000 100644 0000000000000000000000000000000000000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 A DF/DF
+:100644 000000 141c1f1642328e4bc46a7d801a71da392e66791e 0000000000000000000000000000000000000000 D DM
+:100644 000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 0000000000000000000000000000000000000000 D DN
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A LL
+:100644 100644 03f24c8c4700babccfd28b654e7e8eac402ad6cd 103d9f89b50b9aad03054b579be5e7aa665f2d57 M MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 M MM
+:100644 100644 bd084b0c27c7b6cc34f11d6d0509a29be3caf970 a716d58de4a570e0038f5c307bd8db34daea021f M MN
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 4c86f9a85fbc5e6804ee2e17a797538fbe785bca M TT
+:000000 100644 0000000000000000000000000000000000000000 8acb8e9750e3f644bf323fcf3d338849db106c77 A Z/AA
+:000000 100644 0000000000000000000000000000000000000000 087494262084cefee7ed484d20c8dc0580791272 A Z/AN
+:100644 000000 879007efae624d2b1307214b24a956f0a8d686a8 0000000000000000000000000000000000000000 D Z/DD
+:100644 000000 9b541b2275c06e3a7b13f28badf5294e2ae63df4 0000000000000000000000000000000000000000 D Z/DM
+:100644 000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a 0000000000000000000000000000000000000000 D Z/DN
+:100644 100644 d41fda41b7ec4de46b43cb7ea42a45001ae393d5 a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9 M Z/MD
+:100644 100644 4ca22bae2527d3d9e1676498a0fba3b355bd1278 61422ba9c2c873416061a88cd40a59a35b576474 M Z/MM
+:100644 100644 b16d7b25b869f2beb124efa53467d8a1550ad694 a5c544c21cfcb07eb80a4d89a5b7d1570002edfd M Z/MN
+EOF
+cat >.test-plain-OB <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 A AA
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D DD
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A DF
+:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M DM
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A LL
+:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M MM
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M NM
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M TT
+:040000 040000 7d670fdcdb9929f6c7dac196ff78689cd1c566a1 1ba523955d5160681af65cb776411f574c1e8155 M Z
+EOF
+cat >.test-recursive-OB <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 A AA
+:100644 000000 bcc68ef997017466d5c9094bcf7692295f588c9a 0000000000000000000000000000000000000000 D DD
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A DF
+:100644 100644 141c1f1642328e4bc46a7d801a71da392e66791e 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 M DM
+:000000 100644 0000000000000000000000000000000000000000 1d41122ebdd7a640f29d3c9cc4f9d70094374762 A LL
+:100644 000000 03f24c8c4700babccfd28b654e7e8eac402ad6cd 0000000000000000000000000000000000000000 D MD
+:100644 100644 b258508afb7ceb449981bd9d63d2d3e971bf8d34 19989d4559aae417fedee240ccf2ba315ea4dc2b M MM
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M NM
+:100644 100644 40c959f984c8b89a2b02520d17f00d717f024397 2ac547ae9614a00d1b28275de608131f7a0e259f M SS
+:100644 100644 4ac13458899ab908ef3b1128fa378daefc88d356 c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M TT
+:000000 100644 0000000000000000000000000000000000000000 6c0b99286d0bce551ac4a7b3dff8b706edff3715 A Z/AA
+:100644 000000 879007efae624d2b1307214b24a956f0a8d686a8 0000000000000000000000000000000000000000 D Z/DD
+:100644 100644 9b541b2275c06e3a7b13f28badf5294e2ae63df4 d77371d15817fcaa57eeec27f770c505ba974ec1 M Z/DM
+:100644 000000 d41fda41b7ec4de46b43cb7ea42a45001ae393d5 0000000000000000000000000000000000000000 D Z/MD
+:100644 100644 4ca22bae2527d3d9e1676498a0fba3b355bd1278 697aad7715a1e7306ca76290a3dd4208fbaeddfa M Z/MM
+:000000 100644 0000000000000000000000000000000000000000 d12979c22fff69c59ca9409e7a8fe3ee25eaee80 A Z/NA
+:100644 000000 a18393c636b98e9bd7296b8b437ea4992b72440c 0000000000000000000000000000000000000000 D Z/ND
+:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M Z/NM
+EOF
+cat >.test-plain-AB <<\EOF
+:100644 100644 ccba72ad3888a3520b39efcf780b9ee64167535d 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 M AA
+:100644 000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 0000000000000000000000000000000000000000 D AN
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A DF
+:040000 000000 6d50f65d3bdab91c63444294d38f08aeff328e42 0000000000000000000000000000000000000000 D DF
+:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 A DM
+:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 A DN
+:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D MD
+:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M MM
+:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M MN
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M NM
+:100644 100644 4c86f9a85fbc5e6804ee2e17a797538fbe785bca c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M TT
+:040000 040000 5e5f22072bb39f6e12cf663a57cb634c76eefb49 1ba523955d5160681af65cb776411f574c1e8155 M Z
+EOF
+cat >.test-recursive-AB <<\EOF
+:100644 100644 ccba72ad3888a3520b39efcf780b9ee64167535d 6aa2b5335b16431a0ef71e5c0a28be69183cf6a2 M AA
+:100644 000000 7e426fb079479fd67f6d81f984e4ec649a44bc25 0000000000000000000000000000000000000000 D AN
+:000000 100644 0000000000000000000000000000000000000000 71420ab81e254145d26d6fc0cddee64c1acd4787 A DF
+:100644 000000 68a6d8b91da11045cf4aa3a5ab9f2a781c701249 0000000000000000000000000000000000000000 D DF/DF
+:000000 100644 0000000000000000000000000000000000000000 3c4d8de5fbad08572bab8e10eef8dbb264cf0231 A DM
+:000000 100644 0000000000000000000000000000000000000000 35abde1506ddf806572ff4d407bd06885d0f8ee9 A DN
+:100644 000000 103d9f89b50b9aad03054b579be5e7aa665f2d57 0000000000000000000000000000000000000000 D MD
+:100644 100644 b431b272d829ff3aa4d1a5085f4394ab4d3305b6 19989d4559aae417fedee240ccf2ba315ea4dc2b M MM
+:100644 100644 a716d58de4a570e0038f5c307bd8db34daea021f bd084b0c27c7b6cc34f11d6d0509a29be3caf970 M MN
+:000000 100644 0000000000000000000000000000000000000000 15885881ea69115351c09b38371f0348a3fb8c67 A NA
+:100644 000000 a4e179e4291e5536a5e1c82e091052772d2c5a93 0000000000000000000000000000000000000000 D ND
+:100644 100644 c8f25781e8f1792e3e40b74225e20553041b5226 cdb9a8c3da571502ac30225e9c17beccb8387983 M NM
+:100644 100644 4c86f9a85fbc5e6804ee2e17a797538fbe785bca c4e4a12231b9fa79a0053cb6077fcb21bb5b135a M TT
+:100644 100644 8acb8e9750e3f644bf323fcf3d338849db106c77 6c0b99286d0bce551ac4a7b3dff8b706edff3715 M Z/AA
+:100644 000000 087494262084cefee7ed484d20c8dc0580791272 0000000000000000000000000000000000000000 D Z/AN
+:000000 100644 0000000000000000000000000000000000000000 d77371d15817fcaa57eeec27f770c505ba974ec1 A Z/DM
+:000000 100644 0000000000000000000000000000000000000000 beb5d38c55283d280685ea21a0e50cfcc0ca064a A Z/DN
+:100644 000000 a79ac3be9377639e1c7d1edf1ae1b3a5f0ccd8a9 0000000000000000000000000000000000000000 D Z/MD
+:100644 100644 61422ba9c2c873416061a88cd40a59a35b576474 697aad7715a1e7306ca76290a3dd4208fbaeddfa M Z/MM
+:100644 100644 a5c544c21cfcb07eb80a4d89a5b7d1570002edfd b16d7b25b869f2beb124efa53467d8a1550ad694 M Z/MN
+:000000 100644 0000000000000000000000000000000000000000 d12979c22fff69c59ca9409e7a8fe3ee25eaee80 A Z/NA
+:100644 000000 a18393c636b98e9bd7296b8b437ea4992b72440c 0000000000000000000000000000000000000000 D Z/ND
+:100644 100644 3fdbe17fd013303a2e981e1ca1c6cd6e72789087 7e09d6a3a14bd630913e8c75693cea32157b606d M Z/NM
+EOF
+
+x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+x40="$x40$x40$x40$x40$x40$x40$x40$x40"
+z40='0000000000000000000000000000000000000000'
+cmp_diff_files_output () {
+ # diff-files never reports additions. Also it does not fill in the
+ # object ID for the changed files because it wants you to look at the
+ # filesystem.
+ sed <"$2" >.test-tmp \
+ -e '/^:000000 /d;s/'$x40'\( [MCRNDU][0-9]*\) /'$z40'\1 /' &&
+ diff "$1" .test-tmp
+}
+
+test_expect_success \
+ 'diff-tree of known trees.' \
+ 'git-diff-tree $tree_O $tree_A >.test-a &&
+ cmp -s .test-a .test-plain-OA'
+
+test_expect_success \
+ 'diff-tree of known trees.' \
+ 'git-diff-tree -r $tree_O $tree_A >.test-a &&
+ cmp -s .test-a .test-recursive-OA'
+
+test_expect_success \
+ 'diff-tree of known trees.' \
+ 'git-diff-tree $tree_O $tree_B >.test-a &&
+ cmp -s .test-a .test-plain-OB'
+
+test_expect_success \
+ 'diff-tree of known trees.' \
+ 'git-diff-tree -r $tree_O $tree_B >.test-a &&
+ cmp -s .test-a .test-recursive-OB'
+
+test_expect_success \
+ 'diff-tree of known trees.' \
+ 'git-diff-tree $tree_A $tree_B >.test-a &&
+ cmp -s .test-a .test-plain-AB'
+
+test_expect_success \
+ 'diff-tree of known trees.' \
+ 'git-diff-tree -r $tree_A $tree_B >.test-a &&
+ cmp -s .test-a .test-recursive-AB'
+
+test_expect_success \
+ 'diff-cache O with A in cache' \
+ 'git-read-tree $tree_A &&
+ git-diff-index --cached $tree_O >.test-a &&
+ cmp -s .test-a .test-recursive-OA'
+
+test_expect_success \
+ 'diff-cache O with B in cache' \
+ 'git-read-tree $tree_B &&
+ git-diff-index --cached $tree_O >.test-a &&
+ cmp -s .test-a .test-recursive-OB'
+
+test_expect_success \
+ 'diff-cache A with B in cache' \
+ 'git-read-tree $tree_B &&
+ git-diff-index --cached $tree_A >.test-a &&
+ cmp -s .test-a .test-recursive-AB'
+
+test_expect_success \
+ 'diff-files with O in cache and A checked out' \
+ 'rm -fr Z [A-Z][A-Z] &&
+ git-read-tree $tree_A &&
+ git-checkout-index -f -a &&
+ git-read-tree -m $tree_O || return 1
+ git-update-index --refresh >/dev/null ;# this can exit non-zero
+ git-diff-files >.test-a &&
+ cmp_diff_files_output .test-a .test-recursive-OA'
+
+test_expect_success \
+ 'diff-files with O in cache and B checked out' \
+ 'rm -fr Z [A-Z][A-Z] &&
+ git-read-tree $tree_B &&
+ git-checkout-index -f -a &&
+ git-read-tree -m $tree_O || return 1
+ git-update-index --refresh >/dev/null ;# this can exit non-zero
+ git-diff-files >.test-a &&
+ cmp_diff_files_output .test-a .test-recursive-OB'
+
+test_expect_success \
+ 'diff-files with A in cache and B checked out' \
+ 'rm -fr Z [A-Z][A-Z] &&
+ git-read-tree $tree_B &&
+ git-checkout-index -f -a &&
+ git-read-tree -m $tree_A || return 1
+ git-update-index --refresh >/dev/null ;# this can exit non-zero
+ git-diff-files >.test-a &&
+ cmp_diff_files_output .test-a .test-recursive-AB'
+
+################################################################
+# Now we have established the baseline, we do not have to
+# rely on individual object ID values that much.
+
+test_expect_success \
+ 'diff-tree O A == diff-tree -R A O' \
+ 'git-diff-tree $tree_O $tree_A >.test-a &&
+ git-diff-tree -R $tree_A $tree_O >.test-b &&
+ cmp -s .test-a .test-b'
+
+test_expect_success \
+ 'diff-tree -r O A == diff-tree -r -R A O' \
+ 'git-diff-tree -r $tree_O $tree_A >.test-a &&
+ git-diff-tree -r -R $tree_A $tree_O >.test-b &&
+ cmp -s .test-a .test-b'
+
+test_expect_success \
+ 'diff-tree B A == diff-tree -R A B' \
+ 'git-diff-tree $tree_B $tree_A >.test-a &&
+ git-diff-tree -R $tree_A $tree_B >.test-b &&
+ cmp -s .test-a .test-b'
+
+test_expect_success \
+ 'diff-tree -r B A == diff-tree -r -R A B' \
+ 'git-diff-tree -r $tree_B $tree_A >.test-a &&
+ git-diff-tree -r -R $tree_A $tree_B >.test-b &&
+ cmp -s .test-a .test-b'
+
+test_done
diff --git a/t/t4003-diff-rename-1.sh b/t/t4003-diff-rename-1.sh
new file mode 100755
index 000000000..27519704d
--- /dev/null
+++ b/t/t4003-diff-rename-1.sh
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='More rename detection
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'cat ../../COPYING >COPYING &&
+ echo frotz >rezrov &&
+ git-update-index --add COPYING rezrov &&
+ tree=$(git-write-tree) &&
+ echo $tree'
+
+test_expect_success \
+ 'prepare work tree' \
+ 'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+ sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+ rm -f COPYING &&
+ git-update-index --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov. work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov. So we say you
+# copy-and-edit one, and rename-and-edit the other. We do not say
+# anything about rezrov.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree >current
+cat >expected <<\EOF
+diff --git a/COPYING b/COPYING.1
+copy from COPYING
+copy to COPYING.1
+--- a/COPYING
++++ b/COPYING.1
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ However, in order to allow a migration to GPLv3 if that seems like
+diff --git a/COPYING b/COPYING.2
+rename from COPYING
+rename to COPYING.2
+--- a/COPYING
++++ b/COPYING.2
+@@ -2 +2 @@
+- Note that the only valid version of the GPL as far as this project
++ Note that the only valid version of the G.P.L as far as this project
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ HOWEVER, in order to allow a migration to G.P.Lv3 if that seems like
+@@ -12 +12 @@
+- This file is licensed under the GPL v2, or a later version
++ This file is licensed under the G.P.L v2, or a later version
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#1)' \
+ 'compare_diff_patch current expected'
+
+test_expect_success \
+ 'prepare work tree again' \
+ 'mv COPYING.2 COPYING &&
+ git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov. work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov. So we say you
+# edited one, and copy-and-edit the other. We do not say
+# anything about rezrov.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-index -C -p $tree >current
+cat >expected <<\EOF
+diff --git a/COPYING b/COPYING
+--- a/COPYING
++++ b/COPYING
+@@ -2 +2 @@
+- Note that the only valid version of the GPL as far as this project
++ Note that the only valid version of the G.P.L as far as this project
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ HOWEVER, in order to allow a migration to G.P.Lv3 if that seems like
+@@ -12 +12 @@
+- This file is licensed under the GPL v2, or a later version
++ This file is licensed under the G.P.L v2, or a later version
+diff --git a/COPYING b/COPYING.1
+copy from COPYING
+copy to COPYING.1
+--- a/COPYING
++++ b/COPYING.1
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ However, in order to allow a migration to GPLv3 if that seems like
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#2)' \
+ 'compare_diff_patch current expected'
+
+test_expect_success \
+ 'prepare work tree once again' \
+ 'cat ../../COPYING >COPYING &&
+ git-update-index --add --remove COPYING COPYING.1'
+
+# tree has COPYING and rezrov. work tree has COPYING and COPYING.1,
+# but COPYING is not edited. We say you copy-and-edit COPYING.1; this
+# is only possible because -C mode now reports the unmodified file to
+# the diff-core. Unchanged rezrov, although being fed to
+# git-diff-index as well, should not be mentioned.
+
+GIT_DIFF_OPTS=--unified=0 \
+ git-diff-index -C --find-copies-harder -p $tree >current
+cat >expected <<\EOF
+diff --git a/COPYING b/COPYING.1
+copy from COPYING
+copy to COPYING.1
+--- a/COPYING
++++ b/COPYING.1
+@@ -6 +6 @@
+- HOWEVER, in order to allow a migration to GPLv3 if that seems like
++ However, in order to allow a migration to GPLv3 if that seems like
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#3)' \
+ 'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh
new file mode 100755
index 000000000..a23aaa0a9
--- /dev/null
+++ b/t/t4004-diff-rename-symlink.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='More rename detection tests.
+
+The rename detection logic should be able to detect pure rename or
+copy of symbolic links, but should not produce rename/copy followed
+by an edit for them.
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'echo xyzzy | tr -d '\\\\'012 >yomin &&
+ ln -s xyzzy frotz &&
+ git-update-index --add frotz yomin &&
+ tree=$(git-write-tree) &&
+ echo $tree'
+
+test_expect_success \
+ 'prepare work tree' \
+ 'mv frotz rezrov &&
+ rm -f yomin &&
+ ln -s xyzzy nitfol &&
+ ln -s xzzzy bozbar &&
+ git-update-index --add --remove frotz rezrov nitfol bozbar yomin'
+
+# tree has frotz pointing at xyzzy, and yomin that contains xyzzy to
+# confuse things. work tree has rezrov (xyzzy) nitfol (xyzzy) and
+# bozbar (xzzzy).
+# rezrov and nitfol are rename/copy of frotz and bozbar should be
+# a new creation.
+
+GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree >current
+cat >expected <<\EOF
+diff --git a/bozbar b/bozbar
+new file mode 120000
+--- /dev/null
++++ b/bozbar
+@@ -0,0 +1 @@
++xzzzy
+\ No newline at end of file
+diff --git a/frotz b/nitfol
+similarity index 100%
+copy from frotz
+copy to nitfol
+diff --git a/frotz b/rezrov
+similarity index 100%
+rename from frotz
+rename to rezrov
+diff --git a/yomin b/yomin
+deleted file mode 100644
+--- a/yomin
++++ /dev/null
+@@ -1 +0,0 @@
+-xyzzy
+\ No newline at end of file
+EOF
+
+test_expect_success \
+ 'validate diff output' \
+ 'compare_diff_patch current expected'
+
+test_done
diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh
new file mode 100755
index 000000000..684fd23a4
--- /dev/null
+++ b/t/t4005-diff-rename-2.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Same rename detection as t4003 but testing diff-raw.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'cat ../../COPYING >COPYING &&
+ echo frotz >rezrov &&
+ git-update-index --add COPYING rezrov &&
+ tree=$(git-write-tree) &&
+ echo $tree'
+
+test_expect_success \
+ 'prepare work tree' \
+ 'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+ sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+ rm -f COPYING &&
+ git-update-index --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov. work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov. We say COPYING.1
+# and COPYING.2 are based on COPYING, and do not say anything about
+# rezrov.
+
+git-diff-index -M $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234 COPYING COPYING.2
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#1)' \
+ 'compare_diff_raw current expected'
+
+################################################################
+
+test_expect_success \
+ 'prepare work tree again' \
+ 'mv COPYING.2 COPYING &&
+ git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov. work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov. We say COPYING.1
+# is based on COPYING and COPYING is still there, and do not say anything
+# about rezrov.
+
+git-diff-index -C $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M COPYING
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#2)' \
+ 'compare_diff_raw current expected'
+
+################################################################
+
+# tree has COPYING and rezrov. work tree has the same COPYING and
+# copy-edited COPYING.1, and unchanged rezrov. We should not say
+# anything about rezrov nor COPYING, since the revised again diff-raw
+# nows how to say Copy.
+
+test_expect_success \
+ 'prepare work tree once again' \
+ 'cat ../../COPYING >COPYING &&
+ git-update-index --add --remove COPYING COPYING.1'
+
+git-diff-index -C --find-copies-harder $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#3)' \
+ 'compare_diff_raw current expected'
+
+test_done
diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh
new file mode 100755
index 000000000..e2a67e963
--- /dev/null
+++ b/t/t4006-diff-mode.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Test mode change diffs.
+
+'
+. ./test-lib.sh
+
+test_expect_success \
+ 'setup' \
+ 'echo frotz >rezrov &&
+ git-update-index --add rezrov &&
+ tree=`git-write-tree` &&
+ echo $tree'
+
+test_expect_success \
+ 'chmod' \
+ 'chmod +x rezrov &&
+ git-update-index rezrov &&
+ git-diff-index $tree >current'
+
+_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
+sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
+echo ":100644 100755 X X M rezrov" >expected
+
+test_expect_success \
+ 'verify' \
+ 'diff -u expected check'
+
+test_done
+
diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh
new file mode 100755
index 000000000..bb6ba6925
--- /dev/null
+++ b/t/t4007-rename-3.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Rename interaction with pathspec.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'mkdir path0 path1 &&
+ cp ../../COPYING path0/COPYING &&
+ git-update-index --add path0/COPYING &&
+ tree=$(git-write-tree) &&
+ echo $tree'
+
+test_expect_success \
+ 'prepare work tree' \
+ 'cp path0/COPYING path1/COPYING &&
+ git-update-index --add --remove path0/COPYING path1/COPYING'
+
+# In the tree, there is only path0/COPYING. In the cache, path0 and
+# path1 both have COPYING and the latter is a copy of path0/COPYING.
+# Comparing the full tree with cache should tell us so.
+
+git-diff-index -C --find-copies-harder $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100 path0/COPYING path1/COPYING
+EOF
+
+test_expect_success \
+ 'validate the result (#1)' \
+ 'compare_diff_raw current expected'
+
+# In the tree, there is only path0/COPYING. In the cache, path0 and
+# path1 both have COPYING and the latter is a copy of path0/COPYING.
+# However when we say we care only about path1, we should just see
+# path1/COPYING suddenly appearing from nowhere, not detected as
+# a copy from path0/COPYING.
+
+git-diff-index -C $tree path1 >current
+
+cat >expected <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A path1/COPYING
+EOF
+
+test_expect_success \
+ 'validate the result (#2)' \
+ 'compare_diff_raw current expected'
+
+test_expect_success \
+ 'tweak work tree' \
+ 'rm -f path0/COPYING &&
+ git-update-index --remove path0/COPYING'
+
+# In the tree, there is only path0/COPYING. In the cache, path0 does
+# not have COPYING anymore and path1 has COPYING which is a copy of
+# path0/COPYING. Showing the full tree with cache should tell us about
+# the rename.
+
+git-diff-index -C $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 path0/COPYING path1/COPYING
+EOF
+
+test_expect_success \
+ 'validate the result (#3)' \
+ 'compare_diff_raw current expected'
+
+# In the tree, there is only path0/COPYING. In the cache, path0 does
+# not have COPYING anymore and path1 has COPYING which is a copy of
+# path0/COPYING. When we say we care only about path1, we should just
+# see path1/COPYING appearing from nowhere.
+
+git-diff-index -C $tree path1 >current
+
+cat >expected <<\EOF
+:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A path1/COPYING
+EOF
+
+test_expect_success \
+ 'validate the result (#4)' \
+ 'compare_diff_raw current expected'
+
+test_done
diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh
new file mode 100755
index 000000000..263ac1ebf
--- /dev/null
+++ b/t/t4008-diff-break-rewrite.sh
@@ -0,0 +1,188 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Break and then rename
+
+We have two very different files, file0 and file1, registered in a tree.
+
+We update file1 so drastically that it is more similar to file0, and
+then remove file0. With -B, changes to file1 should be broken into
+separate delete and create, resulting in removal of file0, removal of
+original file1 and creation of completely rewritten file1.
+
+Further, with -B and -M together, these three modifications should
+turn into rename-edit of file0 into file1.
+
+Starting from the same two files in the tree, we swap file0 and file1.
+With -B, this should be detected as two complete rewrites, resulting in
+four changes in total.
+
+Further, with -B and -M together, these should turn into two renames.
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+ setup \
+ 'cat ../../README >file0 &&
+ cat ../../COPYING >file1 &&
+ git-update-index --add file0 file1 &&
+ tree=$(git-write-tree) &&
+ echo "$tree"'
+
+test_expect_success \
+ 'change file1 with copy-edit of file0 and remove file0' \
+ 'sed -e "s/git/GIT/" file0 >file1 &&
+ rm -f file0 &&
+ git-update-index --remove file0 file1'
+
+test_expect_success \
+ 'run diff with -B' \
+ 'git-diff-index -B --cached "$tree" >current'
+
+cat >expected <<\EOF
+:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 11e331465a89c394dc25c780de230043750c1ec8 M100 file1
+EOF
+
+test_expect_success \
+ 'validate result of -B (#1)' \
+ 'compare_diff_raw expected current'
+
+test_expect_success \
+ 'run diff with -B and -M' \
+ 'git-diff-index -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c R100 file0 file1
+EOF
+
+test_expect_success \
+ 'validate result of -B -M (#2)' \
+ 'compare_diff_raw expected current'
+
+test_expect_success \
+ 'swap file0 and file1' \
+ 'rm -f file0 file1 &&
+ git-read-tree -m $tree &&
+ git-checkout-index -f -u -a &&
+ mv file0 tmp &&
+ mv file1 file0 &&
+ mv tmp file1 &&
+ git-update-index file0 file1'
+
+test_expect_success \
+ 'run diff with -B' \
+ 'git-diff-index -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100 file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1
+EOF
+
+test_expect_success \
+ 'validate result of -B (#3)' \
+ 'compare_diff_raw expected current'
+
+test_expect_success \
+ 'run diff with -B and -M' \
+ 'git-diff-index -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 file1 file0
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R100 file0 file1
+EOF
+
+test_expect_success \
+ 'validate result of -B -M (#4)' \
+ 'compare_diff_raw expected current'
+
+test_expect_success \
+ 'make file0 into something completely different' \
+ 'rm -f file0 &&
+ ln -s frotz file0 &&
+ git-update-index file0 file1'
+
+test_expect_success \
+ 'run diff with -B' \
+ 'git-diff-index -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1
+EOF
+
+test_expect_success \
+ 'validate result of -B (#5)' \
+ 'compare_diff_raw expected current'
+
+test_expect_success \
+ 'run diff with -B' \
+ 'git-diff-index -B -M "$tree" >current'
+
+# This should not mistake file0 as the copy source of new file1
+# due to type differences.
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1
+EOF
+
+test_expect_success \
+ 'validate result of -B -M (#6)' \
+ 'compare_diff_raw expected current'
+
+test_expect_success \
+ 'run diff with -M' \
+ 'git-diff-index -M "$tree" >current'
+
+# This should not mistake file0 as the copy source of new file1
+# due to type differences.
+cat >expected <<\EOF
+:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M file1
+EOF
+
+test_expect_success \
+ 'validate result of -M (#7)' \
+ 'compare_diff_raw expected current'
+
+test_expect_success \
+ 'file1 edited to look like file0 and file0 rename-edited to file2' \
+ 'rm -f file0 file1 &&
+ git-read-tree -m $tree &&
+ git-checkout-index -f -u -a &&
+ sed -e "s/git/GIT/" file0 >file1 &&
+ sed -e "s/git/GET/" file0 >file2 &&
+ rm -f file0
+ git-update-index --add --remove file0 file1 file2'
+
+test_expect_success \
+ 'run diff with -B' \
+ 'git-diff-index -B "$tree" >current'
+
+cat >expected <<\EOF
+:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D file0
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 08bb2fb671deff4c03a4d4a0a1315dff98d5732c M100 file1
+:000000 100644 0000000000000000000000000000000000000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 A file2
+EOF
+
+test_expect_success \
+ 'validate result of -B (#8)' \
+ 'compare_diff_raw expected current'
+
+test_expect_success \
+ 'run diff with -B -M' \
+ 'git-diff-index -B -M "$tree" >current'
+
+cat >expected <<\EOF
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095 file0 file1
+:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 59f832e5c8b3f7e486be15ad0cd3e95ba9af8998 R095 file0 file2
+EOF
+
+test_expect_success \
+ 'validate result of -B -M (#9)' \
+ 'compare_diff_raw expected current'
+
+test_done
diff --git a/t/t4009-diff-rename-4.sh b/t/t4009-diff-rename-4.sh
new file mode 100755
index 000000000..2f2f8b121
--- /dev/null
+++ b/t/t4009-diff-rename-4.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Same rename detection as t4003 but testing diff-raw -z.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'cat ../../COPYING >COPYING &&
+ echo frotz >rezrov &&
+ git-update-index --add COPYING rezrov &&
+ tree=$(git-write-tree) &&
+ echo $tree'
+
+test_expect_success \
+ 'prepare work tree' \
+ 'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
+ sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
+ rm -f COPYING &&
+ git-update-index --add --remove COPYING COPYING.?'
+
+# tree has COPYING and rezrov. work tree has COPYING.1 and COPYING.2,
+# both are slightly edited, and unchanged rezrov. We say COPYING.1
+# and COPYING.2 are based on COPYING, and do not say anything about
+# rezrov.
+
+git-diff-index -z -M $tree >current
+
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234
+COPYING
+COPYING.2
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#1)' \
+ 'compare_diff_raw_z current expected'
+
+################################################################
+
+test_expect_success \
+ 'prepare work tree again' \
+ 'mv COPYING.2 COPYING &&
+ git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+
+# tree has COPYING and rezrov. work tree has COPYING and COPYING.1,
+# both are slightly edited, and unchanged rezrov. We say COPYING.1
+# is based on COPYING and COPYING is still there, and do not say anything
+# about rezrov.
+
+git-diff-index -z -C $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M
+COPYING
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#2)' \
+ 'compare_diff_raw_z current expected'
+
+################################################################
+
+# tree has COPYING and rezrov. work tree has the same COPYING and
+# copy-edited COPYING.1, and unchanged rezrov. We should not say
+# anything about rezrov nor COPYING, since the revised again diff-raw
+# nows how to say Copy.
+
+test_expect_success \
+ 'prepare work tree once again' \
+ 'cat ../../COPYING >COPYING &&
+ git-update-index --add --remove COPYING COPYING.1'
+
+git-diff-index -z -C --find-copies-harder $tree >current
+cat >expected <<\EOF
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
+COPYING
+COPYING.1
+EOF
+
+test_expect_success \
+ 'validate output from rename/copy detection (#3)' \
+ 'compare_diff_raw_z current expected'
+
+test_done
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
new file mode 100755
index 000000000..8db329d7f
--- /dev/null
+++ b/t/t4010-diff-pathspec.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Pathspec restrictions
+
+Prepare:
+ file0
+ path1/file1
+'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+test_expect_success \
+ setup \
+ 'echo frotz >file0 &&
+ mkdir path1 &&
+ echo rezrov >path1/file1 &&
+ git-update-index --add file0 path1/file1 &&
+ tree=`git-write-tree` &&
+ echo "$tree" &&
+ echo nitfol >file0 &&
+ echo yomin >path1/file1 &&
+ git-update-index file0 path1/file1'
+
+cat >expected <<\EOF
+EOF
+test_expect_success \
+ 'limit to path should show nothing' \
+ 'git-diff-index --cached $tree path >current &&
+ compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M path1/file1
+EOF
+test_expect_success \
+ 'limit to path1 should show path1/file1' \
+ 'git-diff-index --cached $tree path1 >current &&
+ compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M path1/file1
+EOF
+test_expect_success \
+ 'limit to path1/ should show path1/file1' \
+ 'git-diff-index --cached $tree path1/ >current &&
+ compare_diff_raw current expected'
+
+cat >expected <<\EOF
+:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M file0
+EOF
+test_expect_success \
+ 'limit to file0 should show file0' \
+ 'git-diff-index --cached $tree file0 >current &&
+ compare_diff_raw current expected'
+
+cat >expected <<\EOF
+EOF
+test_expect_success \
+ 'limit to file0/ should emit nothing.' \
+ 'git-diff-index --cached $tree file0/ >current &&
+ compare_diff_raw current expected'
+
+test_done
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh
new file mode 100755
index 000000000..6579f06b0
--- /dev/null
+++ b/t/t4100-apply-stat.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply --stat --summary test.
+
+'
+. ./test-lib.sh
+
+test_expect_success \
+ 'rename' \
+ 'git-apply --stat --summary <../t4100/t-apply-1.patch >current &&
+ diff -u ../t4100/t-apply-1.expect current'
+
+test_expect_success \
+ 'copy' \
+ 'git-apply --stat --summary <../t4100/t-apply-2.patch >current &&
+ diff -u ../t4100/t-apply-2.expect current'
+
+test_expect_success \
+ 'rewrite' \
+ 'git-apply --stat --summary <../t4100/t-apply-3.patch >current &&
+ diff -u ../t4100/t-apply-3.expect current'
+
+test_expect_success \
+ 'mode' \
+ 'git-apply --stat --summary <../t4100/t-apply-4.patch >current &&
+ diff -u ../t4100/t-apply-4.expect current'
+
+test_expect_success \
+ 'non git' \
+ 'git-apply --stat --summary <../t4100/t-apply-5.patch >current &&
+ diff -u ../t4100/t-apply-5.expect current'
+
+test_expect_success \
+ 'non git' \
+ 'git-apply --stat --summary <../t4100/t-apply-6.patch >current &&
+ diff -u ../t4100/t-apply-6.expect current'
+
+test_expect_success \
+ 'non git' \
+ 'git-apply --stat --summary <../t4100/t-apply-7.patch >current &&
+ diff -u ../t4100/t-apply-7.expect current'
+
+test_done
+
diff --git a/t/t4100/t-apply-1.expect b/t/t4100/t-apply-1.expect
new file mode 100644
index 000000000..540e64db8
--- /dev/null
+++ b/t/t4100/t-apply-1.expect
@@ -0,0 +1,11 @@
+ Documentation/git-ssh-pull.txt | 12 ++++++------
+ Documentation/git-ssh-push.txt | 10 +++++-----
+ Documentation/git.txt | 6 +++---
+ Makefile | 6 +++---
+ ssh-pull.c | 4 ++--
+ ssh-push.c | 14 +++++++-------
+ 6 files changed, 26 insertions(+), 26 deletions(-)
+ rename Documentation/{git-rpull.txt => git-ssh-pull.txt} (90%)
+ rename Documentation/{git-rpush.txt => git-ssh-push.txt} (71%)
+ rename rpull.c => ssh-pull.c (97%)
+ rename rpush.c => ssh-push.c (93%)
diff --git a/t/t4100/t-apply-1.patch b/t/t4100/t-apply-1.patch
new file mode 100644
index 000000000..de587517f
--- /dev/null
+++ b/t/t4100/t-apply-1.patch
@@ -0,0 +1,194 @@
+418aaf847a8b3ffffb4f777a2dd5262ca5ce0ef7 (from dc93841715dfa9a9cdda6f2c4a25eec831ea7aa0)
+diff --git a/Documentation/git-rpull.txt b/Documentation/git-ssh-pull.txt
+similarity index 90%
+rename from Documentation/git-rpull.txt
+rename to Documentation/git-ssh-pull.txt
+--- a/Documentation/git-rpull.txt
++++ b/Documentation/git-ssh-pull.txt
+@@ -1,21 +1,21 @@
+-git-rpull(1)
+-============
++git-ssh-pull(1)
++===============
+ v0.1, May 2005
+
+ NAME
+ ----
+-git-rpull - Pulls from a remote repository over ssh connection
++git-ssh-pull - Pulls from a remote repository over ssh connection
+
+
+
+ SYNOPSIS
+ --------
+-'git-rpull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
++'git-ssh-pull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
+
+ DESCRIPTION
+ -----------
+-Pulls from a remote repository over ssh connection, invoking git-rpush on
+-the other end.
++Pulls from a remote repository over ssh connection, invoking git-ssh-push
++on the other end.
+
+ OPTIONS
+ -------
+diff --git a/Documentation/git-rpush.txt b/Documentation/git-ssh-push.txt
+similarity index 71%
+rename from Documentation/git-rpush.txt
+rename to Documentation/git-ssh-push.txt
+--- a/Documentation/git-rpush.txt
++++ b/Documentation/git-ssh-push.txt
+@@ -1,19 +1,19 @@
+-git-rpush(1)
+-============
++git-ssh-push(1)
++===============
+ v0.1, May 2005
+
+ NAME
+ ----
+-git-rpush - Helper "server-side" program used by git-rpull
++git-ssh-push - Helper "server-side" program used by git-ssh-pull
+
+
+ SYNOPSIS
+ --------
+-'git-rpush'
++'git-ssh-push'
+
+ DESCRIPTION
+ -----------
+-Helper "server-side" program used by git-rpull.
++Helper "server-side" program used by git-ssh-pull.
+
+
+ Author
+diff --git a/Documentation/git.txt b/Documentation/git.txt
+--- a/Documentation/git.txt
++++ b/Documentation/git.txt
+@@ -148,7 +148,7 @@ link:git-resolve-script.html[git-resolve
+ link:git-tag-script.html[git-tag-script]::
+ An example script to create a tag object signed with GPG
+
+-link:git-rpull.html[git-rpull]::
++link:git-ssh-pull.html[git-ssh-pull]::
+ Pulls from a remote repository over ssh connection
+
+ Interogators:
+@@ -156,8 +156,8 @@ Interogators:
+ link:git-diff-helper.html[git-diff-helper]::
+ Generates patch format output for git-diff-*
+
+-link:git-rpush.html[git-rpush]::
+- Helper "server-side" program used by git-rpull
++link:git-ssh-push.html[git-ssh-push]::
++ Helper "server-side" program used by git-ssh-pull
+
+
+
+diff --git a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -30,7 +30,7 @@ PROG= git-update-cache git-diff-files
+ git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
+ git-check-files git-ls-tree git-merge-base git-merge-cache \
+ git-unpack-file git-export git-diff-cache git-convert-cache \
+- git-http-pull git-rpush git-rpull git-rev-list git-mktag \
++ git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
+ git-diff-helper git-tar-tree git-local-pull git-write-blob \
+ git-get-tar-commit-id git-mkdelta git-apply git-stripspace
+
+@@ -105,8 +105,8 @@ git-diff-cache: diff-cache.c
+ git-convert-cache: convert-cache.c
+ git-http-pull: http-pull.c pull.c
+ git-local-pull: local-pull.c pull.c
+-git-rpush: rsh.c
+-git-rpull: rsh.c pull.c
++git-ssh-push: rsh.c
++git-ssh-pull: rsh.c pull.c
+ git-rev-list: rev-list.c
+ git-mktag: mktag.c
+ git-diff-helper: diff-helper.c
+diff --git a/rpull.c b/ssh-pull.c
+similarity index 97%
+rename from rpull.c
+rename to ssh-pull.c
+--- a/rpull.c
++++ b/ssh-pull.c
+@@ -64,13 +64,13 @@ int main(int argc, char **argv)
+ arg++;
+ }
+ if (argc < arg + 2) {
+- usage("git-rpull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
++ usage("git-ssh-pull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
+ return 1;
+ }
+ commit_id = argv[arg];
+ url = argv[arg + 1];
+
+- if (setup_connection(&fd_in, &fd_out, "git-rpush", url, arg, argv + 1))
++ if (setup_connection(&fd_in, &fd_out, "git-ssh-push", url, arg, argv + 1))
+ return 1;
+
+ if (get_version())
+diff --git a/rpush.c b/ssh-push.c
+similarity index 93%
+rename from rpush.c
+rename to ssh-push.c
+--- a/rpush.c
++++ b/ssh-push.c
+@@ -16,7 +16,7 @@ int serve_object(int fd_in, int fd_out)
+ do {
+ size = read(fd_in, sha1 + posn, 20 - posn);
+ if (size < 0) {
+- perror("git-rpush: read ");
++ perror("git-ssh-push: read ");
+ return -1;
+ }
+ if (!size)
+@@ -30,7 +30,7 @@ int serve_object(int fd_in, int fd_out)
+ buf = map_sha1_file(sha1, &objsize);
+
+ if (!buf) {
+- fprintf(stderr, "git-rpush: could not find %s\n",
++ fprintf(stderr, "git-ssh-push: could not find %s\n",
+ sha1_to_hex(sha1));
+ remote = -1;
+ }
+@@ -45,9 +45,9 @@ int serve_object(int fd_in, int fd_out)
+ size = write(fd_out, buf + posn, objsize - posn);
+ if (size <= 0) {
+ if (!size) {
+- fprintf(stderr, "git-rpush: write closed");
++ fprintf(stderr, "git-ssh-push: write closed");
+ } else {
+- perror("git-rpush: write ");
++ perror("git-ssh-push: write ");
+ }
+ return -1;
+ }
+@@ -71,7 +71,7 @@ void service(int fd_in, int fd_out) {
+ retval = read(fd_in, &type, 1);
+ if (retval < 1) {
+ if (retval < 0)
+- perror("rpush: read ");
++ perror("git-ssh-push: read ");
+ return;
+ }
+ if (type == 'v' && serve_version(fd_in, fd_out))
+@@ -91,12 +91,12 @@ int main(int argc, char **argv)
+ arg++;
+ }
+ if (argc < arg + 2) {
+- usage("git-rpush [-c] [-t] [-a] commit-id url");
++ usage("git-ssh-push [-c] [-t] [-a] commit-id url");
+ return 1;
+ }
+ commit_id = argv[arg];
+ url = argv[arg + 1];
+- if (setup_connection(&fd_in, &fd_out, "git-rpull", url, arg, argv + 1))
++ if (setup_connection(&fd_in, &fd_out, "git-ssh-pull", url, arg, argv + 1))
+ return 1;
+
+ service(fd_in, fd_out);
diff --git a/t/t4100/t-apply-2.expect b/t/t4100/t-apply-2.expect
new file mode 100644
index 000000000..d1e645974
--- /dev/null
+++ b/t/t4100/t-apply-2.expect
@@ -0,0 +1,5 @@
+ Makefile | 2 +-
+ git-fetch-script | 5 -----
+ git-pull-script | 34 +---------------------------------
+ 3 files changed, 2 insertions(+), 39 deletions(-)
+ copy git-pull-script => git-fetch-script (87%)
diff --git a/t/t4100/t-apply-2.patch b/t/t4100/t-apply-2.patch
new file mode 100644
index 000000000..cfdc80885
--- /dev/null
+++ b/t/t4100/t-apply-2.patch
@@ -0,0 +1,72 @@
+7ef76925d9c19ef74874e1735e2436e56d0c4897 (from 6b14d7faf0bad026a81a27bac07b47691f621b8f)
+diff --git a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -20,7 +20,7 @@ INSTALL=install
+
+ SCRIPTS=git-apply-patch-script git-merge-one-file-script git-prune-script \
+ git-pull-script git-tag-script git-resolve-script git-whatchanged \
+- git-deltafy-script
++ git-deltafy-script git-fetch-script
+
+ PROG= git-update-cache git-diff-files git-init-db git-write-tree \
+ git-read-tree git-commit-tree git-cat-file git-fsck-cache \
+diff --git a/git-pull-script b/git-fetch-script
+similarity index 87%
+copy from git-pull-script
+copy to git-fetch-script
+--- a/git-pull-script
++++ b/git-fetch-script
+@@ -39,8 +39,3 @@ download_one "$merge_repo/$merge_name" "
+
+ echo "Getting object database"
+ download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
+-
+-git-resolve-script \
+- "$(cat "$GIT_DIR"/HEAD)" \
+- "$(cat "$GIT_DIR"/MERGE_HEAD)" \
+- "$merge_repo"
+diff --git a/git-pull-script b/git-pull-script
+--- a/git-pull-script
++++ b/git-pull-script
+@@ -6,39 +6,7 @@ merge_name=${2:-HEAD}
+ : ${GIT_DIR=.git}
+ : ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+
+-download_one () {
+- # remote_path="$1" local_file="$2"
+- case "$1" in
+- http://*)
+- wget -q -O "$2" "$1" ;;
+- /*)
+- test -f "$1" && cat >"$2" "$1" ;;
+- *)
+- rsync -L "$1" "$2" ;;
+- esac
+-}
+-
+-download_objects () {
+- # remote_repo="$1" head_sha1="$2"
+- case "$1" in
+- http://*)
+- git-http-pull -a "$2" "$1/"
+- ;;
+- /*)
+- git-local-pull -l -a "$2" "$1/"
+- ;;
+- *)
+- rsync -avz --ignore-existing \
+- "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
+- ;;
+- esac
+-}
+-
+-echo "Getting remote $merge_name"
+-download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
+-
+-echo "Getting object database"
+-download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
++git-fetch-script "$merge_repo" "$merge_name"
+
+ git-resolve-script \
+ "$(cat "$GIT_DIR"/HEAD)" \
diff --git a/t/t4100/t-apply-3.expect b/t/t4100/t-apply-3.expect
new file mode 100644
index 000000000..912a552a7
--- /dev/null
+++ b/t/t4100/t-apply-3.expect
@@ -0,0 +1,7 @@
+ Documentation/git-ls-tree.txt | 20 +-
+ ls-tree.c | 459 ++++++++++++++++++++++-------------------
+ t/t3100-ls-tree-restrict.sh | 3
+ tree.c | 2
+ tree.h | 1
+ 5 files changed, 262 insertions(+), 223 deletions(-)
+ rewrite ls-tree.c (82%)
diff --git a/t/t4100/t-apply-3.patch b/t/t4100/t-apply-3.patch
new file mode 100644
index 000000000..90cdbaa5b
--- /dev/null
+++ b/t/t4100/t-apply-3.patch
@@ -0,0 +1,567 @@
+6af1f0192ff8740fe77db7cf02c739ccfbdf119c (from 2bc2564145835996734d6ed5d1880f85b17233d6)
+diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
+--- a/Documentation/git-ls-tree.txt
++++ b/Documentation/git-ls-tree.txt
+@@ -4,23 +4,26 @@ v0.1, May 2005
+
+ NAME
+ ----
+-git-ls-tree - Displays a tree object in human readable form
++git-ls-tree - Lists the contents of a tree object.
+
+
+ SYNOPSIS
+ --------
+-'git-ls-tree' [-r] [-z] <tree-ish> [paths...]
++'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...]
+
+ DESCRIPTION
+ -----------
+-Converts the tree object to a human readable (and script processable)
+-form.
++Lists the contents of a tree object, like what "/bin/ls -a" does
++in the current working directory.
+
+ OPTIONS
+ -------
+ <tree-ish>::
+ Id of a tree.
+
++-d::
++ show only the named tree entry itself, not its children
++
+ -r::
+ recurse into sub-trees
+
+@@ -28,18 +31,19 @@ OPTIONS
+ \0 line termination on output
+
+ paths::
+- Optionally, restrict the output of git-ls-tree to specific
+- paths. Directories will only list their tree blob ids.
+- Implies -r.
++ When paths are given, shows them. Otherwise implicitly
++ uses the root level of the tree as the sole path argument.
++
+
+ Output Format
+ -------------
+- <mode>\t <type>\t <object>\t <file>
++ <mode> SP <type> SP <object> TAB <file>
+
+
+ Author
+ ------
+ Written by Linus Torvalds <torvalds@osdl.org>
++Completely rewritten from scratch by Junio C Hamano <junkio@cox.net>
+
+ Documentation
+ --------------
+diff --git a/ls-tree.c b/ls-tree.c
+dissimilarity index 82%
+--- ls-tree.c
++++ ls-tree.c
+@@ -1,212 +1,247 @@
+-/*
+- * GIT - The information manager from hell
+- *
+- * Copyright (C) Linus Torvalds, 2005
+- */
+-#include "cache.h"
+-
+-static int line_termination = '\n';
+-static int recursive = 0;
+-
+-struct path_prefix {
+- struct path_prefix *prev;
+- const char *name;
+-};
+-
+-#define DEBUG(fmt, ...)
+-
+-static int string_path_prefix(char *buff, size_t blen, struct path_prefix *prefix)
+-{
+- int len = 0;
+- if (prefix) {
+- if (prefix->prev) {
+- len = string_path_prefix(buff,blen,prefix->prev);
+- buff += len;
+- blen -= len;
+- if (blen > 0) {
+- *buff = '/';
+- len++;
+- buff++;
+- blen--;
+- }
+- }
+- strncpy(buff,prefix->name,blen);
+- return len + strlen(prefix->name);
+- }
+-
+- return 0;
+-}
+-
+-static void print_path_prefix(struct path_prefix *prefix)
+-{
+- if (prefix) {
+- if (prefix->prev) {
+- print_path_prefix(prefix->prev);
+- putchar('/');
+- }
+- fputs(prefix->name, stdout);
+- }
+-}
+-
+-/*
+- * return:
+- * -1 if prefix is *not* a subset of path
+- * 0 if prefix == path
+- * 1 if prefix is a subset of path
+- */
+-static int pathcmp(const char *path, struct path_prefix *prefix)
+-{
+- char buff[PATH_MAX];
+- int len,slen;
+-
+- if (prefix == NULL)
+- return 1;
+-
+- len = string_path_prefix(buff, sizeof buff, prefix);
+- slen = strlen(path);
+-
+- if (slen < len)
+- return -1;
+-
+- if (strncmp(path,buff,len) == 0) {
+- if (slen == len)
+- return 0;
+- else
+- return 1;
+- }
+-
+- return -1;
+-}
+-
+-/*
+- * match may be NULL, or a *sorted* list of paths
+- */
+-static void list_recursive(void *buffer,
+- const char *type,
+- unsigned long size,
+- struct path_prefix *prefix,
+- char **match, int matches)
+-{
+- struct path_prefix this_prefix;
+- this_prefix.prev = prefix;
+-
+- if (strcmp(type, "tree"))
+- die("expected a 'tree' node");
+-
+- if (matches)
+- recursive = 1;
+-
+- while (size) {
+- int namelen = strlen(buffer)+1;
+- void *eltbuf = NULL;
+- char elttype[20];
+- unsigned long eltsize;
+- unsigned char *sha1 = buffer + namelen;
+- char *path = strchr(buffer, ' ') + 1;
+- unsigned int mode;
+- const char *matched = NULL;
+- int mtype = -1;
+- int mindex;
+-
+- if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
+- die("corrupt 'tree' file");
+- buffer = sha1 + 20;
+- size -= namelen + 20;
+-
+- this_prefix.name = path;
+- for ( mindex = 0; mindex < matches; mindex++) {
+- mtype = pathcmp(match[mindex],&this_prefix);
+- if (mtype >= 0) {
+- matched = match[mindex];
+- break;
+- }
+- }
+-
+- /*
+- * If we're not matching, or if this is an exact match,
+- * print out the info
+- */
+- if (!matches || (matched != NULL && mtype == 0)) {
+- printf("%06o %s %s\t", mode,
+- S_ISDIR(mode) ? "tree" : "blob",
+- sha1_to_hex(sha1));
+- print_path_prefix(&this_prefix);
+- putchar(line_termination);
+- }
+-
+- if (! recursive || ! S_ISDIR(mode))
+- continue;
+-
+- if (matches && ! matched)
+- continue;
+-
+- if (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {
+- error("cannot read %s", sha1_to_hex(sha1));
+- continue;
+- }
+-
+- /* If this is an exact directory match, we may have
+- * directory files following this path. Match on them.
+- * Otherwise, we're at a pach subcomponent, and we need
+- * to try to match again.
+- */
+- if (mtype == 0)
+- mindex++;
+-
+- list_recursive(eltbuf, elttype, eltsize, &this_prefix, &match[mindex], matches-mindex);
+- free(eltbuf);
+- }
+-}
+-
+-static int qcmp(const void *a, const void *b)
+-{
+- return strcmp(*(char **)a, *(char **)b);
+-}
+-
+-static int list(unsigned char *sha1,char **path)
+-{
+- void *buffer;
+- unsigned long size;
+- int npaths;
+-
+- for (npaths = 0; path[npaths] != NULL; npaths++)
+- ;
+-
+- qsort(path,npaths,sizeof(char *),qcmp);
+-
+- buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+- if (!buffer)
+- die("unable to read sha1 file");
+- list_recursive(buffer, "tree", size, NULL, path, npaths);
+- free(buffer);
+- return 0;
+-}
+-
+-static const char *ls_tree_usage = "git-ls-tree [-r] [-z] <key> [paths...]";
+-
+-int main(int argc, char **argv)
+-{
+- unsigned char sha1[20];
+-
+- while (1 < argc && argv[1][0] == '-') {
+- switch (argv[1][1]) {
+- case 'z':
+- line_termination = 0;
+- break;
+- case 'r':
+- recursive = 1;
+- break;
+- default:
+- usage(ls_tree_usage);
+- }
+- argc--; argv++;
+- }
+-
+- if (argc < 2)
+- usage(ls_tree_usage);
+- if (get_sha1(argv[1], sha1) < 0)
+- usage(ls_tree_usage);
+- if (list(sha1, &argv[2]) < 0)
+- die("list failed");
+- return 0;
+-}
++/*
++ * GIT - The information manager from hell
++ *
++ * Copyright (C) Linus Torvalds, 2005
++ */
++#include "cache.h"
++#include "blob.h"
++#include "tree.h"
++
++static int line_termination = '\n';
++#define LS_RECURSIVE 1
++#define LS_TREE_ONLY 2
++static int ls_options = 0;
++
++static struct tree_entry_list root_entry;
++
++static void prepare_root(unsigned char *sha1)
++{
++ unsigned char rsha[20];
++ unsigned long size;
++ void *buf;
++ struct tree *root_tree;
++
++ buf = read_object_with_reference(sha1, "tree", &size, rsha);
++ free(buf);
++ if (!buf)
++ die("Could not read %s", sha1_to_hex(sha1));
++
++ root_tree = lookup_tree(rsha);
++ if (!root_tree)
++ die("Could not read %s", sha1_to_hex(sha1));
++
++ /* Prepare a fake entry */
++ root_entry.directory = 1;
++ root_entry.executable = root_entry.symlink = 0;
++ root_entry.mode = S_IFDIR;
++ root_entry.name = "";
++ root_entry.item.tree = root_tree;
++ root_entry.parent = NULL;
++}
++
++static int prepare_children(struct tree_entry_list *elem)
++{
++ if (!elem->directory)
++ return -1;
++ if (!elem->item.tree->object.parsed) {
++ struct tree_entry_list *e;
++ if (parse_tree(elem->item.tree))
++ return -1;
++ /* Set up the parent link */
++ for (e = elem->item.tree->entries; e; e = e->next)
++ e->parent = elem;
++ }
++ return 0;
++}
++
++static struct tree_entry_list *find_entry_0(struct tree_entry_list *elem,
++ const char *path,
++ const char *path_end)
++{
++ const char *ep;
++ int len;
++
++ while (path < path_end) {
++ if (prepare_children(elem))
++ return NULL;
++
++ /* In elem->tree->entries, find the one that has name
++ * that matches what is between path and ep.
++ */
++ elem = elem->item.tree->entries;
++
++ ep = strchr(path, '/');
++ if (!ep || path_end <= ep)
++ ep = path_end;
++ len = ep - path;
++
++ while (elem) {
++ if ((strlen(elem->name) == len) &&
++ !strncmp(elem->name, path, len))
++ break;
++ elem = elem->next;
++ }
++ if (path_end <= ep || !elem)
++ return elem;
++ while (*ep == '/' && ep < path_end)
++ ep++;
++ path = ep;
++ }
++ return NULL;
++}
++
++static struct tree_entry_list *find_entry(const char *path,
++ const char *path_end)
++{
++ /* Find tree element, descending from root, that
++ * corresponds to the named path, lazily expanding
++ * the tree if possible.
++ */
++ if (path == path_end) {
++ /* Special. This is the root level */
++ return &root_entry;
++ }
++ return find_entry_0(&root_entry, path, path_end);
++}
++
++static void show_entry_name(struct tree_entry_list *e)
++{
++ /* This is yucky. The root level is there for
++ * our convenience but we really want to do a
++ * forest.
++ */
++ if (e->parent && e->parent != &root_entry) {
++ show_entry_name(e->parent);
++ putchar('/');
++ }
++ printf("%s", e->name);
++}
++
++static const char *entry_type(struct tree_entry_list *e)
++{
++ return (e->directory ? "tree" : "blob");
++}
++
++static const char *entry_hex(struct tree_entry_list *e)
++{
++ return sha1_to_hex(e->directory
++ ? e->item.tree->object.sha1
++ : e->item.blob->object.sha1);
++}
++
++/* forward declaration for mutually recursive routines */
++static int show_entry(struct tree_entry_list *, int);
++
++static int show_children(struct tree_entry_list *e, int level)
++{
++ if (prepare_children(e))
++ die("internal error: ls-tree show_children called with non tree");
++ e = e->item.tree->entries;
++ while (e) {
++ show_entry(e, level);
++ e = e->next;
++ }
++ return 0;
++}
++
++static int show_entry(struct tree_entry_list *e, int level)
++{
++ int err = 0;
++
++ if (e != &root_entry) {
++ printf("%06o %s %s ", e->mode, entry_type(e),
++ entry_hex(e));
++ show_entry_name(e);
++ putchar(line_termination);
++ }
++
++ if (e->directory) {
++ /* If this is a directory, we have the following cases:
++ * (1) This is the top-level request (explicit path from the
++ * command line, or "root" if there is no command line).
++ * a. Without any flag. We show direct children. We do not
++ * recurse into them.
++ * b. With -r. We do recurse into children.
++ * c. With -d. We do not recurse into children.
++ * (2) We came here because our caller is either (1-a) or
++ * (1-b).
++ * a. Without any flag. We do not show our children (which
++ * are grandchildren for the original request).
++ * b. With -r. We continue to recurse into our children.
++ * c. With -d. We should not have come here to begin with.
++ */
++ if (level == 0 && !(ls_options & LS_TREE_ONLY))
++ /* case (1)-a and (1)-b */
++ err = err | show_children(e, level+1);
++ else if (level && ls_options & LS_RECURSIVE)
++ /* case (2)-b */
++ err = err | show_children(e, level+1);
++ }
++ return err;
++}
++
++static int list_one(const char *path, const char *path_end)
++{
++ int err = 0;
++ struct tree_entry_list *e = find_entry(path, path_end);
++ if (!e) {
++ /* traditionally ls-tree does not complain about
++ * missing path. We may change this later to match
++ * what "/bin/ls -a" does, which is to complain.
++ */
++ return err;
++ }
++ err = err | show_entry(e, 0);
++ return err;
++}
++
++static int list(char **path)
++{
++ int i;
++ int err = 0;
++ for (i = 0; path[i]; i++) {
++ int len = strlen(path[i]);
++ while (0 <= len && path[i][len] == '/')
++ len--;
++ err = err | list_one(path[i], path[i] + len);
++ }
++ return err;
++}
++
++static const char *ls_tree_usage =
++ "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
++
++int main(int argc, char **argv)
++{
++ static char *path0[] = { "", NULL };
++ char **path;
++ unsigned char sha1[20];
++
++ while (1 < argc && argv[1][0] == '-') {
++ switch (argv[1][1]) {
++ case 'z':
++ line_termination = 0;
++ break;
++ case 'r':
++ ls_options |= LS_RECURSIVE;
++ break;
++ case 'd':
++ ls_options |= LS_TREE_ONLY;
++ break;
++ default:
++ usage(ls_tree_usage);
++ }
++ argc--; argv++;
++ }
++
++ if (argc < 2)
++ usage(ls_tree_usage);
++ if (get_sha1(argv[1], sha1) < 0)
++ usage(ls_tree_usage);
++
++ path = (argc == 2) ? path0 : (argv + 2);
++ prepare_root(sha1);
++ if (list(path) < 0)
++ die("list failed");
++ return 0;
++}
+diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
+--- a/t/t3100-ls-tree-restrict.sh
++++ b/t/t3100-ls-tree-restrict.sh
+@@ -74,8 +74,8 @@ test_expect_success \
+ 'ls-tree filtered' \
+ 'git-ls-tree $tree path1 path0 >current &&
+ cat >expected <<\EOF &&
+-100644 blob X path0
+ 120000 blob X path1
++100644 blob X path0
+ EOF
+ test_output'
+
+@@ -85,7 +85,6 @@ test_expect_success \
+ cat >expected <<\EOF &&
+ 040000 tree X path2
+ 040000 tree X path2/baz
+-100644 blob X path2/baz/b
+ 120000 blob X path2/bazbo
+ 100644 blob X path2/foo
+ EOF
+diff --git a/tree.c b/tree.c
+--- a/tree.c
++++ b/tree.c
+@@ -133,7 +133,7 @@ int parse_tree_buffer(struct tree *item,
+ }
+ if (obj)
+ add_ref(&item->object, obj);
+-
++ entry->parent = NULL; /* needs to be filled by the user */
+ *list_p = entry;
+ list_p = &entry->next;
+ }
+diff --git a/tree.h b/tree.h
+--- a/tree.h
++++ b/tree.h
+@@ -16,6 +16,7 @@ struct tree_entry_list {
+ struct tree *tree;
+ struct blob *blob;
+ } item;
++ struct tree_entry_list *parent;
+ };
+
+ struct tree {
diff --git a/t/t4100/t-apply-4.expect b/t/t4100/t-apply-4.expect
new file mode 100644
index 000000000..1ec028b3d
--- /dev/null
+++ b/t/t4100/t-apply-4.expect
@@ -0,0 +1,5 @@
+ t/t0000-basic.sh | 0
+ t/test-lib.sh | 0
+ 2 files changed, 0 insertions(+), 0 deletions(-)
+ mode change 100644 => 100755 t/t0000-basic.sh
+ mode change 100644 => 100755 t/test-lib.sh
diff --git a/t/t4100/t-apply-4.patch b/t/t4100/t-apply-4.patch
new file mode 100644
index 000000000..4a56ab5cf
--- /dev/null
+++ b/t/t4100/t-apply-4.patch
@@ -0,0 +1,7 @@
+ceede59ea90cebad52ba9c8263fef3fb6ef17593 (from 368f99d57e8ed17243f2e164431449d48bfca2fb)
+diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
+old mode 100644
+new mode 100755
+diff --git a/t/test-lib.sh b/t/test-lib.sh
+old mode 100644
+new mode 100755
diff --git a/t/t4100/t-apply-5.expect b/t/t4100/t-apply-5.expect
new file mode 100644
index 000000000..b387df15d
--- /dev/null
+++ b/t/t4100/t-apply-5.expect
@@ -0,0 +1,19 @@
+ Documentation/git-rpull.txt | 50 -------------------
+ Documentation/git-rpush.txt | 30 ------------
+ Documentation/git-ssh-pull.txt | 50 +++++++++++++++++++
+ Documentation/git-ssh-push.txt | 30 ++++++++++++
+ Documentation/git.txt | 6 +-
+ Makefile | 6 +-
+ rpull.c | 83 --------------------------------
+ rpush.c | 104 ----------------------------------------
+ ssh-pull.c | 83 ++++++++++++++++++++++++++++++++
+ ssh-push.c | 104 ++++++++++++++++++++++++++++++++++++++++
+ 10 files changed, 273 insertions(+), 273 deletions(-)
+ delete Documentation/git-rpull.txt
+ delete Documentation/git-rpush.txt
+ create Documentation/git-ssh-pull.txt
+ create Documentation/git-ssh-push.txt
+ delete rpull.c
+ delete rpush.c
+ create ssh-pull.c
+ create ssh-push.c
diff --git a/t/t4100/t-apply-5.patch b/t/t4100/t-apply-5.patch
new file mode 100644
index 000000000..de11623d1
--- /dev/null
+++ b/t/t4100/t-apply-5.patch
@@ -0,0 +1,612 @@
+diff a/Documentation/git-rpull.txt b/Documentation/git-rpull.txt
+--- a/Documentation/git-rpull.txt
++++ /dev/null
+@@ -1,50 +0,0 @@
+-git-rpull(1)
+-============
+-v0.1, May 2005
+-
+-NAME
+-----
+-git-rpull - Pulls from a remote repository over ssh connection
+-
+-
+-
+-SYNOPSIS
+---------
+-'git-rpull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
+-
+-DESCRIPTION
+------------
+-Pulls from a remote repository over ssh connection, invoking git-rpush on
+-the other end.
+-
+-OPTIONS
+--------
+--c::
+- Get the commit objects.
+--t::
+- Get trees associated with the commit objects.
+--a::
+- Get all the objects.
+--d::
+- Do not check for delta base objects (use this option
+- only when you know the remote repository is not
+- deltified).
+---recover::
+- Check dependency of deltified object more carefully than
+- usual, to recover after earlier pull that was interrupted.
+--v::
+- Report what is downloaded.
+-
+-
+-Author
+-------
+-Written by Linus Torvalds <torvalds@osdl.org>
+-
+-Documentation
+---------------
+-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+-
+-GIT
+----
+-Part of the link:git.html[git] suite
+-
+diff a/Documentation/git-rpush.txt b/Documentation/git-rpush.txt
+--- a/Documentation/git-rpush.txt
++++ /dev/null
+@@ -1,30 +0,0 @@
+-git-rpush(1)
+-============
+-v0.1, May 2005
+-
+-NAME
+-----
+-git-rpush - Helper "server-side" program used by git-rpull
+-
+-
+-SYNOPSIS
+---------
+-'git-rpush'
+-
+-DESCRIPTION
+------------
+-Helper "server-side" program used by git-rpull.
+-
+-
+-Author
+-------
+-Written by Linus Torvalds <torvalds@osdl.org>
+-
+-Documentation
+---------------
+-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+-
+-GIT
+----
+-Part of the link:git.html[git] suite
+-
+diff a/Documentation/git-ssh-pull.txt b/Documentation/git-ssh-pull.txt
+--- /dev/null
++++ b/Documentation/git-ssh-pull.txt
+@@ -0,0 +1,50 @@
++git-ssh-pull(1)
++===============
++v0.1, May 2005
++
++NAME
++----
++git-ssh-pull - Pulls from a remote repository over ssh connection
++
++
++
++SYNOPSIS
++--------
++'git-ssh-pull' [-c] [-t] [-a] [-d] [-v] [--recover] commit-id url
++
++DESCRIPTION
++-----------
++Pulls from a remote repository over ssh connection, invoking git-ssh-push
++on the other end.
++
++OPTIONS
++-------
++-c::
++ Get the commit objects.
++-t::
++ Get trees associated with the commit objects.
++-a::
++ Get all the objects.
++-d::
++ Do not check for delta base objects (use this option
++ only when you know the remote repository is not
++ deltified).
++--recover::
++ Check dependency of deltified object more carefully than
++ usual, to recover after earlier pull that was interrupted.
++-v::
++ Report what is downloaded.
++
++
++Author
++------
++Written by Linus Torvalds <torvalds@osdl.org>
++
++Documentation
++--------------
++Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
++
++GIT
++---
++Part of the link:git.html[git] suite
++
+diff a/Documentation/git-ssh-push.txt b/Documentation/git-ssh-push.txt
+--- /dev/null
++++ b/Documentation/git-ssh-push.txt
+@@ -0,0 +1,30 @@
++git-ssh-push(1)
++===============
++v0.1, May 2005
++
++NAME
++----
++git-ssh-push - Helper "server-side" program used by git-ssh-pull
++
++
++SYNOPSIS
++--------
++'git-ssh-push'
++
++DESCRIPTION
++-----------
++Helper "server-side" program used by git-ssh-pull.
++
++
++Author
++------
++Written by Linus Torvalds <torvalds@osdl.org>
++
++Documentation
++--------------
++Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
++
++GIT
++---
++Part of the link:git.html[git] suite
++
+diff a/Documentation/git.txt b/Documentation/git.txt
+--- a/Documentation/git.txt
++++ b/Documentation/git.txt
+@@ -148,7 +148,7 @@ link:git-resolve-script.html[git-resolve
+ link:git-tag-script.html[git-tag-script]::
+ An example script to create a tag object signed with GPG
+
+-link:git-rpull.html[git-rpull]::
++link:git-ssh-pull.html[git-ssh-pull]::
+ Pulls from a remote repository over ssh connection
+
+ Interogators:
+@@ -156,8 +156,8 @@ Interogators:
+ link:git-diff-helper.html[git-diff-helper]::
+ Generates patch format output for git-diff-*
+
+-link:git-rpush.html[git-rpush]::
+- Helper "server-side" program used by git-rpull
++link:git-ssh-push.html[git-ssh-push]::
++ Helper "server-side" program used by git-ssh-pull
+
+
+
+diff a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -30,7 +30,7 @@ PROG= git-update-cache git-diff-files
+ git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
+ git-check-files git-ls-tree git-merge-base git-merge-cache \
+ git-unpack-file git-export git-diff-cache git-convert-cache \
+- git-http-pull git-rpush git-rpull git-rev-list git-mktag \
++ git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
+ git-diff-helper git-tar-tree git-local-pull git-write-blob \
+ git-get-tar-commit-id git-mkdelta git-apply git-stripspace
+
+@@ -105,8 +105,8 @@ git-diff-cache: diff-cache.c
+ git-convert-cache: convert-cache.c
+ git-http-pull: http-pull.c pull.c
+ git-local-pull: local-pull.c pull.c
+-git-rpush: rsh.c
+-git-rpull: rsh.c pull.c
++git-ssh-push: rsh.c
++git-ssh-pull: rsh.c pull.c
+ git-rev-list: rev-list.c
+ git-mktag: mktag.c
+ git-diff-helper: diff-helper.c
+diff a/rpull.c b/rpull.c
+--- a/rpull.c
++++ /dev/null
+@@ -1,83 +0,0 @@
+-#include "cache.h"
+-#include "commit.h"
+-#include "rsh.h"
+-#include "pull.h"
+-
+-static int fd_in;
+-static int fd_out;
+-
+-static unsigned char remote_version = 0;
+-static unsigned char local_version = 1;
+-
+-int fetch(unsigned char *sha1)
+-{
+- int ret;
+- signed char remote;
+- char type = 'o';
+- if (has_sha1_file(sha1))
+- return 0;
+- write(fd_out, &type, 1);
+- write(fd_out, sha1, 20);
+- if (read(fd_in, &remote, 1) < 1)
+- return -1;
+- if (remote < 0)
+- return remote;
+- ret = write_sha1_from_fd(sha1, fd_in);
+- if (!ret)
+- pull_say("got %s\n", sha1_to_hex(sha1));
+- return ret;
+-}
+-
+-int get_version(void)
+-{
+- char type = 'v';
+- write(fd_out, &type, 1);
+- write(fd_out, &local_version, 1);
+- if (read(fd_in, &remote_version, 1) < 1) {
+- return error("Couldn't read version from remote end");
+- }
+- return 0;
+-}
+-
+-int main(int argc, char **argv)
+-{
+- char *commit_id;
+- char *url;
+- int arg = 1;
+-
+- while (arg < argc && argv[arg][0] == '-') {
+- if (argv[arg][1] == 't') {
+- get_tree = 1;
+- } else if (argv[arg][1] == 'c') {
+- get_history = 1;
+- } else if (argv[arg][1] == 'd') {
+- get_delta = 0;
+- } else if (!strcmp(argv[arg], "--recover")) {
+- get_delta = 2;
+- } else if (argv[arg][1] == 'a') {
+- get_all = 1;
+- get_tree = 1;
+- get_history = 1;
+- } else if (argv[arg][1] == 'v') {
+- get_verbosely = 1;
+- }
+- arg++;
+- }
+- if (argc < arg + 2) {
+- usage("git-rpull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
+- return 1;
+- }
+- commit_id = argv[arg];
+- url = argv[arg + 1];
+-
+- if (setup_connection(&fd_in, &fd_out, "git-rpush", url, arg, argv + 1))
+- return 1;
+-
+- if (get_version())
+- return 1;
+-
+- if (pull(commit_id))
+- return 1;
+-
+- return 0;
+-}
+diff a/rpush.c b/rpush.c
+--- a/rpush.c
++++ /dev/null
+@@ -1,104 +0,0 @@
+-#include "cache.h"
+-#include "rsh.h"
+-#include <sys/socket.h>
+-#include <errno.h>
+-
+-unsigned char local_version = 1;
+-unsigned char remote_version = 0;
+-
+-int serve_object(int fd_in, int fd_out) {
+- ssize_t size;
+- int posn = 0;
+- char sha1[20];
+- unsigned long objsize;
+- void *buf;
+- signed char remote;
+- do {
+- size = read(fd_in, sha1 + posn, 20 - posn);
+- if (size < 0) {
+- perror("git-rpush: read ");
+- return -1;
+- }
+- if (!size)
+- return -1;
+- posn += size;
+- } while (posn < 20);
+-
+- /* fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1)); */
+- remote = 0;
+-
+- buf = map_sha1_file(sha1, &objsize);
+-
+- if (!buf) {
+- fprintf(stderr, "git-rpush: could not find %s\n",
+- sha1_to_hex(sha1));
+- remote = -1;
+- }
+-
+- write(fd_out, &remote, 1);
+-
+- if (remote < 0)
+- return 0;
+-
+- posn = 0;
+- do {
+- size = write(fd_out, buf + posn, objsize - posn);
+- if (size <= 0) {
+- if (!size) {
+- fprintf(stderr, "git-rpush: write closed");
+- } else {
+- perror("git-rpush: write ");
+- }
+- return -1;
+- }
+- posn += size;
+- } while (posn < objsize);
+- return 0;
+-}
+-
+-int serve_version(int fd_in, int fd_out)
+-{
+- if (read(fd_in, &remote_version, 1) < 1)
+- return -1;
+- write(fd_out, &local_version, 1);
+- return 0;
+-}
+-
+-void service(int fd_in, int fd_out) {
+- char type;
+- int retval;
+- do {
+- retval = read(fd_in, &type, 1);
+- if (retval < 1) {
+- if (retval < 0)
+- perror("rpush: read ");
+- return;
+- }
+- if (type == 'v' && serve_version(fd_in, fd_out))
+- return;
+- if (type == 'o' && serve_object(fd_in, fd_out))
+- return;
+- } while (1);
+-}
+-
+-int main(int argc, char **argv)
+-{
+- int arg = 1;
+- char *commit_id;
+- char *url;
+- int fd_in, fd_out;
+- while (arg < argc && argv[arg][0] == '-') {
+- arg++;
+- }
+- if (argc < arg + 2) {
+- usage("git-rpush [-c] [-t] [-a] commit-id url");
+- return 1;
+- }
+- commit_id = argv[arg];
+- url = argv[arg + 1];
+- if (setup_connection(&fd_in, &fd_out, "git-rpull", url, arg, argv + 1))
+- return 1;
+-
+- service(fd_in, fd_out);
+- return 0;
+-}
+diff a/ssh-pull.c b/ssh-pull.c
+--- /dev/null
++++ b/ssh-pull.c
+@@ -0,0 +1,83 @@
++#include "cache.h"
++#include "commit.h"
++#include "rsh.h"
++#include "pull.h"
++
++static int fd_in;
++static int fd_out;
++
++static unsigned char remote_version = 0;
++static unsigned char local_version = 1;
++
++int fetch(unsigned char *sha1)
++{
++ int ret;
++ signed char remote;
++ char type = 'o';
++ if (has_sha1_file(sha1))
++ return 0;
++ write(fd_out, &type, 1);
++ write(fd_out, sha1, 20);
++ if (read(fd_in, &remote, 1) < 1)
++ return -1;
++ if (remote < 0)
++ return remote;
++ ret = write_sha1_from_fd(sha1, fd_in);
++ if (!ret)
++ pull_say("got %s\n", sha1_to_hex(sha1));
++ return ret;
++}
++
++int get_version(void)
++{
++ char type = 'v';
++ write(fd_out, &type, 1);
++ write(fd_out, &local_version, 1);
++ if (read(fd_in, &remote_version, 1) < 1) {
++ return error("Couldn't read version from remote end");
++ }
++ return 0;
++}
++
++int main(int argc, char **argv)
++{
++ char *commit_id;
++ char *url;
++ int arg = 1;
++
++ while (arg < argc && argv[arg][0] == '-') {
++ if (argv[arg][1] == 't') {
++ get_tree = 1;
++ } else if (argv[arg][1] == 'c') {
++ get_history = 1;
++ } else if (argv[arg][1] == 'd') {
++ get_delta = 0;
++ } else if (!strcmp(argv[arg], "--recover")) {
++ get_delta = 2;
++ } else if (argv[arg][1] == 'a') {
++ get_all = 1;
++ get_tree = 1;
++ get_history = 1;
++ } else if (argv[arg][1] == 'v') {
++ get_verbosely = 1;
++ }
++ arg++;
++ }
++ if (argc < arg + 2) {
++ usage("git-ssh-pull [-c] [-t] [-a] [-v] [-d] [--recover] commit-id url");
++ return 1;
++ }
++ commit_id = argv[arg];
++ url = argv[arg + 1];
++
++ if (setup_connection(&fd_in, &fd_out, "git-ssh-push", url, arg, argv + 1))
++ return 1;
++
++ if (get_version())
++ return 1;
++
++ if (pull(commit_id))
++ return 1;
++
++ return 0;
++}
+diff a/ssh-push.c b/ssh-push.c
+--- /dev/null
++++ b/ssh-push.c
+@@ -0,0 +1,104 @@
++#include "cache.h"
++#include "rsh.h"
++#include <sys/socket.h>
++#include <errno.h>
++
++unsigned char local_version = 1;
++unsigned char remote_version = 0;
++
++int serve_object(int fd_in, int fd_out) {
++ ssize_t size;
++ int posn = 0;
++ char sha1[20];
++ unsigned long objsize;
++ void *buf;
++ signed char remote;
++ do {
++ size = read(fd_in, sha1 + posn, 20 - posn);
++ if (size < 0) {
++ perror("git-ssh-push: read ");
++ return -1;
++ }
++ if (!size)
++ return -1;
++ posn += size;
++ } while (posn < 20);
++
++ /* fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1)); */
++ remote = 0;
++
++ buf = map_sha1_file(sha1, &objsize);
++
++ if (!buf) {
++ fprintf(stderr, "git-ssh-push: could not find %s\n",
++ sha1_to_hex(sha1));
++ remote = -1;
++ }
++
++ write(fd_out, &remote, 1);
++
++ if (remote < 0)
++ return 0;
++
++ posn = 0;
++ do {
++ size = write(fd_out, buf + posn, objsize - posn);
++ if (size <= 0) {
++ if (!size) {
++ fprintf(stderr, "git-ssh-push: write closed");
++ } else {
++ perror("git-ssh-push: write ");
++ }
++ return -1;
++ }
++ posn += size;
++ } while (posn < objsize);
++ return 0;
++}
++
++int serve_version(int fd_in, int fd_out)
++{
++ if (read(fd_in, &remote_version, 1) < 1)
++ return -1;
++ write(fd_out, &local_version, 1);
++ return 0;
++}
++
++void service(int fd_in, int fd_out) {
++ char type;
++ int retval;
++ do {
++ retval = read(fd_in, &type, 1);
++ if (retval < 1) {
++ if (retval < 0)
++ perror("git-ssh-push: read ");
++ return;
++ }
++ if (type == 'v' && serve_version(fd_in, fd_out))
++ return;
++ if (type == 'o' && serve_object(fd_in, fd_out))
++ return;
++ } while (1);
++}
++
++int main(int argc, char **argv)
++{
++ int arg = 1;
++ char *commit_id;
++ char *url;
++ int fd_in, fd_out;
++ while (arg < argc && argv[arg][0] == '-') {
++ arg++;
++ }
++ if (argc < arg + 2) {
++ usage("git-ssh-push [-c] [-t] [-a] commit-id url");
++ return 1;
++ }
++ commit_id = argv[arg];
++ url = argv[arg + 1];
++ if (setup_connection(&fd_in, &fd_out, "git-ssh-pull", url, arg, argv + 1))
++ return 1;
++
++ service(fd_in, fd_out);
++ return 0;
++}
diff --git a/t/t4100/t-apply-6.expect b/t/t4100/t-apply-6.expect
new file mode 100644
index 000000000..1c343d459
--- /dev/null
+++ b/t/t4100/t-apply-6.expect
@@ -0,0 +1,5 @@
+ Makefile | 2 +-
+ git-fetch-script | 41 +++++++++++++++++++++++++++++++++++++++++
+ git-pull-script | 34 +---------------------------------
+ 3 files changed, 43 insertions(+), 34 deletions(-)
+ create git-fetch-script
diff --git a/t/t4100/t-apply-6.patch b/t/t4100/t-apply-6.patch
new file mode 100644
index 000000000..d9753637f
--- /dev/null
+++ b/t/t4100/t-apply-6.patch
@@ -0,0 +1,101 @@
+diff a/Makefile b/Makefile
+--- a/Makefile
++++ b/Makefile
+@@ -20,7 +20,7 @@ INSTALL=install
+
+ SCRIPTS=git-apply-patch-script git-merge-one-file-script git-prune-script \
+ git-pull-script git-tag-script git-resolve-script git-whatchanged \
+- git-deltafy-script
++ git-deltafy-script git-fetch-script
+
+ PROG= git-update-cache git-diff-files git-init-db git-write-tree \
+ git-read-tree git-commit-tree git-cat-file git-fsck-cache \
+diff a/git-fetch-script b/git-fetch-script
+--- /dev/null
++++ b/git-fetch-script
+@@ -0,0 +1,41 @@
++#!/bin/sh
++#
++merge_repo=$1
++merge_name=${2:-HEAD}
++
++: ${GIT_DIR=.git}
++: ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
++
++download_one () {
++ # remote_path="$1" local_file="$2"
++ case "$1" in
++ http://*)
++ wget -q -O "$2" "$1" ;;
++ /*)
++ test -f "$1" && cat >"$2" "$1" ;;
++ *)
++ rsync -L "$1" "$2" ;;
++ esac
++}
++
++download_objects () {
++ # remote_repo="$1" head_sha1="$2"
++ case "$1" in
++ http://*)
++ git-http-pull -a "$2" "$1/"
++ ;;
++ /*)
++ git-local-pull -l -a "$2" "$1/"
++ ;;
++ *)
++ rsync -avz --ignore-existing \
++ "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
++ ;;
++ esac
++}
++
++echo "Getting remote $merge_name"
++download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
++
++echo "Getting object database"
++download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
+diff a/git-pull-script b/git-pull-script
+--- a/git-pull-script
++++ b/git-pull-script
+@@ -6,39 +6,7 @@ merge_name=${2:-HEAD}
+ : ${GIT_DIR=.git}
+ : ${GIT_OBJECT_DIRECTORY="${SHA1_FILE_DIRECTORY-"$GIT_DIR/objects"}"}
+
+-download_one () {
+- # remote_path="$1" local_file="$2"
+- case "$1" in
+- http://*)
+- wget -q -O "$2" "$1" ;;
+- /*)
+- test -f "$1" && cat >"$2" "$1" ;;
+- *)
+- rsync -L "$1" "$2" ;;
+- esac
+-}
+-
+-download_objects () {
+- # remote_repo="$1" head_sha1="$2"
+- case "$1" in
+- http://*)
+- git-http-pull -a "$2" "$1/"
+- ;;
+- /*)
+- git-local-pull -l -a "$2" "$1/"
+- ;;
+- *)
+- rsync -avz --ignore-existing \
+- "$1/objects/." "$GIT_OBJECT_DIRECTORY"/.
+- ;;
+- esac
+-}
+-
+-echo "Getting remote $merge_name"
+-download_one "$merge_repo/$merge_name" "$GIT_DIR"/MERGE_HEAD
+-
+-echo "Getting object database"
+-download_objects "$merge_repo" "$(cat "$GIT_DIR"/MERGE_HEAD)"
++git-fetch-script "$merge_repo" "$merge_name"
+
+ git-resolve-script \
+ "$(cat "$GIT_DIR"/HEAD)" \
diff --git a/t/t4100/t-apply-7.expect b/t/t4100/t-apply-7.expect
new file mode 100644
index 000000000..1283164d9
--- /dev/null
+++ b/t/t4100/t-apply-7.expect
@@ -0,0 +1,6 @@
+ Documentation/git-ls-tree.txt | 20 +-
+ ls-tree.c | 333 +++++++++++++++++++++++------------------
+ t/t3100-ls-tree-restrict.sh | 3
+ tree.c | 2
+ tree.h | 1
+ 5 files changed, 199 insertions(+), 160 deletions(-)
diff --git a/t/t4100/t-apply-7.patch b/t/t4100/t-apply-7.patch
new file mode 100644
index 000000000..07c6589e7
--- /dev/null
+++ b/t/t4100/t-apply-7.patch
@@ -0,0 +1,494 @@
+diff a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
+--- a/Documentation/git-ls-tree.txt
++++ b/Documentation/git-ls-tree.txt
+@@ -4,23 +4,26 @@ v0.1, May 2005
+
+ NAME
+ ----
+-git-ls-tree - Displays a tree object in human readable form
++git-ls-tree - Lists the contents of a tree object.
+
+
+ SYNOPSIS
+ --------
+-'git-ls-tree' [-r] [-z] <tree-ish> [paths...]
++'git-ls-tree' [-d] [-r] [-z] <tree-ish> [paths...]
+
+ DESCRIPTION
+ -----------
+-Converts the tree object to a human readable (and script processable)
+-form.
++Lists the contents of a tree object, like what "/bin/ls -a" does
++in the current working directory.
+
+ OPTIONS
+ -------
+ <tree-ish>::
+ Id of a tree.
+
++-d::
++ show only the named tree entry itself, not its children
++
+ -r::
+ recurse into sub-trees
+
+@@ -28,18 +31,19 @@ OPTIONS
+ \0 line termination on output
+
+ paths::
+- Optionally, restrict the output of git-ls-tree to specific
+- paths. Directories will only list their tree blob ids.
+- Implies -r.
++ When paths are given, shows them. Otherwise implicitly
++ uses the root level of the tree as the sole path argument.
++
+
+ Output Format
+ -------------
+- <mode>\t <type>\t <object>\t <file>
++ <mode> SP <type> SP <object> TAB <file>
+
+
+ Author
+ ------
+ Written by Linus Torvalds <torvalds@osdl.org>
++Completely rewritten from scratch by Junio C Hamano <junkio@cox.net>
+
+ Documentation
+ --------------
+diff a/ls-tree.c b/ls-tree.c
+--- a/ls-tree.c
++++ b/ls-tree.c
+@@ -4,188 +4,217 @@
+ * Copyright (C) Linus Torvalds, 2005
+ */
+ #include "cache.h"
++#include "blob.h"
++#include "tree.h"
+
+ static int line_termination = '\n';
+-static int recursive = 0;
++#define LS_RECURSIVE 1
++#define LS_TREE_ONLY 2
++static int ls_options = 0;
+
+-struct path_prefix {
+- struct path_prefix *prev;
+- const char *name;
+-};
+-
+-#define DEBUG(fmt, ...)
+-
+-static int string_path_prefix(char *buff, size_t blen, struct path_prefix *prefix)
+-{
+- int len = 0;
+- if (prefix) {
+- if (prefix->prev) {
+- len = string_path_prefix(buff,blen,prefix->prev);
+- buff += len;
+- blen -= len;
+- if (blen > 0) {
+- *buff = '/';
+- len++;
+- buff++;
+- blen--;
+- }
+- }
+- strncpy(buff,prefix->name,blen);
+- return len + strlen(prefix->name);
+- }
++static struct tree_entry_list root_entry;
+
+- return 0;
++static void prepare_root(unsigned char *sha1)
++{
++ unsigned char rsha[20];
++ unsigned long size;
++ void *buf;
++ struct tree *root_tree;
++
++ buf = read_object_with_reference(sha1, "tree", &size, rsha);
++ free(buf);
++ if (!buf)
++ die("Could not read %s", sha1_to_hex(sha1));
++
++ root_tree = lookup_tree(rsha);
++ if (!root_tree)
++ die("Could not read %s", sha1_to_hex(sha1));
++
++ /* Prepare a fake entry */
++ root_entry.directory = 1;
++ root_entry.executable = root_entry.symlink = 0;
++ root_entry.mode = S_IFDIR;
++ root_entry.name = "";
++ root_entry.item.tree = root_tree;
++ root_entry.parent = NULL;
+ }
+
+-static void print_path_prefix(struct path_prefix *prefix)
++static int prepare_children(struct tree_entry_list *elem)
+ {
+- if (prefix) {
+- if (prefix->prev) {
+- print_path_prefix(prefix->prev);
+- putchar('/');
+- }
+- fputs(prefix->name, stdout);
++ if (!elem->directory)
++ return -1;
++ if (!elem->item.tree->object.parsed) {
++ struct tree_entry_list *e;
++ if (parse_tree(elem->item.tree))
++ return -1;
++ /* Set up the parent link */
++ for (e = elem->item.tree->entries; e; e = e->next)
++ e->parent = elem;
+ }
++ return 0;
+ }
+
+-/*
+- * return:
+- * -1 if prefix is *not* a subset of path
+- * 0 if prefix == path
+- * 1 if prefix is a subset of path
+- */
+-static int pathcmp(const char *path, struct path_prefix *prefix)
+-{
+- char buff[PATH_MAX];
+- int len,slen;
++static struct tree_entry_list *find_entry_0(struct tree_entry_list *elem,
++ const char *path,
++ const char *path_end)
++{
++ const char *ep;
++ int len;
++
++ while (path < path_end) {
++ if (prepare_children(elem))
++ return NULL;
+
+- if (prefix == NULL)
+- return 1;
++ /* In elem->tree->entries, find the one that has name
++ * that matches what is between path and ep.
++ */
++ elem = elem->item.tree->entries;
+
+- len = string_path_prefix(buff, sizeof buff, prefix);
+- slen = strlen(path);
++ ep = strchr(path, '/');
++ if (!ep || path_end <= ep)
++ ep = path_end;
++ len = ep - path;
++
++ while (elem) {
++ if ((strlen(elem->name) == len) &&
++ !strncmp(elem->name, path, len))
++ break;
++ elem = elem->next;
++ }
++ if (path_end <= ep || !elem)
++ return elem;
++ while (*ep == '/' && ep < path_end)
++ ep++;
++ path = ep;
++ }
++ return NULL;
++}
+
+- if (slen < len)
+- return -1;
++static struct tree_entry_list *find_entry(const char *path,
++ const char *path_end)
++{
++ /* Find tree element, descending from root, that
++ * corresponds to the named path, lazily expanding
++ * the tree if possible.
++ */
++ if (path == path_end) {
++ /* Special. This is the root level */
++ return &root_entry;
++ }
++ return find_entry_0(&root_entry, path, path_end);
++}
+
+- if (strncmp(path,buff,len) == 0) {
+- if (slen == len)
+- return 0;
+- else
+- return 1;
++static void show_entry_name(struct tree_entry_list *e)
++{
++ /* This is yucky. The root level is there for
++ * our convenience but we really want to do a
++ * forest.
++ */
++ if (e->parent && e->parent != &root_entry) {
++ show_entry_name(e->parent);
++ putchar('/');
+ }
++ printf("%s", e->name);
++}
+
+- return -1;
+-}
++static const char *entry_type(struct tree_entry_list *e)
++{
++ return (e->directory ? "tree" : "blob");
++}
+
+-/*
+- * match may be NULL, or a *sorted* list of paths
+- */
+-static void list_recursive(void *buffer,
+- const char *type,
+- unsigned long size,
+- struct path_prefix *prefix,
+- char **match, int matches)
+-{
+- struct path_prefix this_prefix;
+- this_prefix.prev = prefix;
+-
+- if (strcmp(type, "tree"))
+- die("expected a 'tree' node");
+-
+- if (matches)
+- recursive = 1;
+-
+- while (size) {
+- int namelen = strlen(buffer)+1;
+- void *eltbuf = NULL;
+- char elttype[20];
+- unsigned long eltsize;
+- unsigned char *sha1 = buffer + namelen;
+- char *path = strchr(buffer, ' ') + 1;
+- unsigned int mode;
+- const char *matched = NULL;
+- int mtype = -1;
+- int mindex;
+-
+- if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
+- die("corrupt 'tree' file");
+- buffer = sha1 + 20;
+- size -= namelen + 20;
+-
+- this_prefix.name = path;
+- for ( mindex = 0; mindex < matches; mindex++) {
+- mtype = pathcmp(match[mindex],&this_prefix);
+- if (mtype >= 0) {
+- matched = match[mindex];
+- break;
+- }
+- }
++static const char *entry_hex(struct tree_entry_list *e)
++{
++ return sha1_to_hex(e->directory
++ ? e->item.tree->object.sha1
++ : e->item.blob->object.sha1);
++}
+
+- /*
+- * If we're not matching, or if this is an exact match,
+- * print out the info
+- */
+- if (!matches || (matched != NULL && mtype == 0)) {
+- printf("%06o %s %s\t", mode,
+- S_ISDIR(mode) ? "tree" : "blob",
+- sha1_to_hex(sha1));
+- print_path_prefix(&this_prefix);
+- putchar(line_termination);
+- }
++/* forward declaration for mutually recursive routines */
++static int show_entry(struct tree_entry_list *, int);
+
+- if (! recursive || ! S_ISDIR(mode))
+- continue;
++static int show_children(struct tree_entry_list *e, int level)
++{
++ if (prepare_children(e))
++ die("internal error: ls-tree show_children called with non tree");
++ e = e->item.tree->entries;
++ while (e) {
++ show_entry(e, level);
++ e = e->next;
++ }
++ return 0;
++}
+
+- if (matches && ! matched)
+- continue;
++static int show_entry(struct tree_entry_list *e, int level)
++{
++ int err = 0;
+
+- if (! (eltbuf = read_sha1_file(sha1, elttype, &eltsize)) ) {
+- error("cannot read %s", sha1_to_hex(sha1));
+- continue;
+- }
++ if (e != &root_entry) {
++ printf("%06o %s %s ", e->mode, entry_type(e),
++ entry_hex(e));
++ show_entry_name(e);
++ putchar(line_termination);
++ }
+
+- /* If this is an exact directory match, we may have
+- * directory files following this path. Match on them.
+- * Otherwise, we're at a pach subcomponent, and we need
+- * to try to match again.
++ if (e->directory) {
++ /* If this is a directory, we have the following cases:
++ * (1) This is the top-level request (explicit path from the
++ * command line, or "root" if there is no command line).
++ * a. Without any flag. We show direct children. We do not
++ * recurse into them.
++ * b. With -r. We do recurse into children.
++ * c. With -d. We do not recurse into children.
++ * (2) We came here because our caller is either (1-a) or
++ * (1-b).
++ * a. Without any flag. We do not show our children (which
++ * are grandchildren for the original request).
++ * b. With -r. We continue to recurse into our children.
++ * c. With -d. We should not have come here to begin with.
+ */
+- if (mtype == 0)
+- mindex++;
+-
+- list_recursive(eltbuf, elttype, eltsize, &this_prefix, &match[mindex], matches-mindex);
+- free(eltbuf);
++ if (level == 0 && !(ls_options & LS_TREE_ONLY))
++ /* case (1)-a and (1)-b */
++ err = err | show_children(e, level+1);
++ else if (level && ls_options & LS_RECURSIVE)
++ /* case (2)-b */
++ err = err | show_children(e, level+1);
+ }
++ return err;
+ }
+
+-static int qcmp(const void *a, const void *b)
++static int list_one(const char *path, const char *path_end)
+ {
+- return strcmp(*(char **)a, *(char **)b);
++ int err = 0;
++ struct tree_entry_list *e = find_entry(path, path_end);
++ if (!e) {
++ /* traditionally ls-tree does not complain about
++ * missing path. We may change this later to match
++ * what "/bin/ls -a" does, which is to complain.
++ */
++ return err;
++ }
++ err = err | show_entry(e, 0);
++ return err;
+ }
+
+-static int list(unsigned char *sha1,char **path)
++static int list(char **path)
+ {
+- void *buffer;
+- unsigned long size;
+- int npaths;
+-
+- for (npaths = 0; path[npaths] != NULL; npaths++)
+- ;
+-
+- qsort(path,npaths,sizeof(char *),qcmp);
+-
+- buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+- if (!buffer)
+- die("unable to read sha1 file");
+- list_recursive(buffer, "tree", size, NULL, path, npaths);
+- free(buffer);
+- return 0;
++ int i;
++ int err = 0;
++ for (i = 0; path[i]; i++) {
++ int len = strlen(path[i]);
++ while (0 <= len && path[i][len] == '/')
++ len--;
++ err = err | list_one(path[i], path[i] + len);
++ }
++ return err;
+ }
+
+-static const char *ls_tree_usage = "git-ls-tree [-r] [-z] <key> [paths...]";
++static const char *ls_tree_usage =
++ "git-ls-tree [-d] [-r] [-z] <tree-ish> [path...]";
+
+ int main(int argc, char **argv)
+ {
++ static char *path0[] = { "", NULL };
++ char **path;
+ unsigned char sha1[20];
+
+ while (1 < argc && argv[1][0] == '-') {
+@@ -194,7 +223,10 @@ int main(int argc, char **argv)
+ line_termination = 0;
+ break;
+ case 'r':
+- recursive = 1;
++ ls_options |= LS_RECURSIVE;
++ break;
++ case 'd':
++ ls_options |= LS_TREE_ONLY;
+ break;
+ default:
+ usage(ls_tree_usage);
+@@ -206,7 +238,10 @@ int main(int argc, char **argv)
+ usage(ls_tree_usage);
+ if (get_sha1(argv[1], sha1) < 0)
+ usage(ls_tree_usage);
+- if (list(sha1, &argv[2]) < 0)
++
++ path = (argc == 2) ? path0 : (argv + 2);
++ prepare_root(sha1);
++ if (list(path) < 0)
+ die("list failed");
+ return 0;
+ }
+diff a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh
+--- a/t/t3100-ls-tree-restrict.sh
++++ b/t/t3100-ls-tree-restrict.sh
+@@ -74,8 +74,8 @@ test_expect_success \
+ 'ls-tree filtered' \
+ 'git-ls-tree $tree path1 path0 >current &&
+ cat >expected <<\EOF &&
+-100644 blob X path0
+ 120000 blob X path1
++100644 blob X path0
+ EOF
+ test_output'
+
+@@ -85,7 +85,6 @@ test_expect_success \
+ cat >expected <<\EOF &&
+ 040000 tree X path2
+ 040000 tree X path2/baz
+-100644 blob X path2/baz/b
+ 120000 blob X path2/bazbo
+ 100644 blob X path2/foo
+ EOF
+diff a/tree.c b/tree.c
+--- a/tree.c
++++ b/tree.c
+@@ -133,7 +133,7 @@ int parse_tree_buffer(struct tree *item,
+ }
+ if (obj)
+ add_ref(&item->object, obj);
+-
++ entry->parent = NULL; /* needs to be filled by the user */
+ *list_p = entry;
+ list_p = &entry->next;
+ }
+diff a/tree.h b/tree.h
+--- a/tree.h
++++ b/tree.h
+@@ -16,6 +16,7 @@ struct tree_entry_list {
+ struct tree *tree;
+ struct blob *blob;
+ } item;
++ struct tree_entry_list *parent;
+ };
+
+ struct tree {
diff --git a/t/t4101-apply-nonl.sh b/t/t4101-apply-nonl.sh
new file mode 100755
index 000000000..26b131d0d
--- /dev/null
+++ b/t/t4101-apply-nonl.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply should handle files with incomplete lines.
+
+'
+. ./test-lib.sh
+
+# setup
+
+(echo a; echo b) >frotz.0
+(echo a; echo b; echo c) >frotz.1
+(echo a; echo b | tr -d '\012') >frotz.2
+(echo a; echo c; echo b | tr -d '\012') >frotz.3
+
+for i in 0 1 2 3
+do
+ for j in 0 1 2 3
+ do
+ test $i -eq $j && continue
+ diff -u frotz.$i frotz.$j |
+ sed -e '
+ /^---/s|.*|--- a/frotz|
+ /^+++/s|.*|+++ b/frotz|' >diff.$i-$j
+ cat frotz.$i >frotz
+ test_expect_success \
+ "apply diff between $i and $j" \
+ "git-apply <diff.$i-$j && diff frotz.$j frotz"
+ done
+done
+
+test_done
diff --git a/t/t4102-apply-rename.sh b/t/t4102-apply-rename.sh
new file mode 100755
index 000000000..0401d7bbc
--- /dev/null
+++ b/t/t4102-apply-rename.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply handling copy/rename patch.
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat >test-patch <<\EOF
+diff --git a/foo b/bar
+similarity index 47%
+copy from foo
+copy to bar
+--- a/foo
++++ b/bar
+@@ -1 +1 @@
+-This is foo
++This is bar
+EOF
+
+echo 'This is foo' >foo
+chmod +x foo
+
+test_expect_success setup \
+ 'git-update-index --add foo'
+
+test_expect_success apply \
+ 'git-apply --index --stat --summary --apply test-patch'
+
+test_expect_success validate \
+ 'test -f bar && ls -l bar | grep "^-..x......"'
+
+test_done
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh
new file mode 100644
index 000000000..00bd8b15c
--- /dev/null
+++ b/t/t4103-apply-binary.sh
@@ -0,0 +1,115 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply handling binary patches
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat >file1 <<EOF
+A quick brown fox jumps over the lazy dog.
+A tiny little penguin runs around in circles.
+There is a flag with Linux written on it.
+A slow black-and-white panda just sits there,
+munching on his bamboo.
+EOF
+cat file1 >file2
+cat file1 >file4
+
+git-update-index --add --remove file1 file2 file4
+git-commit -m 'Initial Version' 2>/dev/null
+
+git-checkout -b binary
+tr 'x' '\0' <file1 >file3
+cat file3 >file4
+git-add file2
+tr '\0' 'v' <file3 >file1
+rm -f file2
+git-update-index --add --remove file1 file2 file3 file4
+git-commit -m 'Second Version'
+
+git-diff-tree -p master binary >B.diff
+git-diff-tree -p -C master binary >C.diff
+
+git-diff-tree -p --full-index master binary >BF.diff
+git-diff-tree -p --full-index -C master binary >CF.diff
+
+test_expect_success 'stat binary diff -- should not fail.' \
+ 'git-checkout master
+ git-apply --stat --summary B.diff'
+
+test_expect_success 'stat binary diff (copy) -- should not fail.' \
+ 'git-checkout master
+ git-apply --stat --summary C.diff'
+
+test_expect_failure 'check binary diff -- should fail.' \
+ 'git-checkout master
+ git-apply --check B.diff'
+
+test_expect_failure 'check binary diff (copy) -- should fail.' \
+ 'git-checkout master
+ git-apply --check C.diff'
+
+test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \
+ 'git-checkout master
+ git-apply --check --allow-binary-replacement B.diff'
+
+test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \
+ 'git-checkout master
+ git-apply --check --allow-binary-replacement C.diff'
+
+test_expect_success 'check binary diff with replacement.' \
+ 'git-checkout master
+ git-apply --check --allow-binary-replacement BF.diff'
+
+test_expect_success 'check binary diff with replacement (copy).' \
+ 'git-checkout master
+ git-apply --check --allow-binary-replacement CF.diff'
+
+# Now we start applying them.
+
+do_reset () {
+ rm -f file?
+ git-reset --hard
+ git-checkout -f master
+}
+
+test_expect_failure 'apply binary diff -- should fail.' \
+ 'do_reset
+ git-apply B.diff'
+
+test_expect_failure 'apply binary diff -- should fail.' \
+ 'do_reset
+ git-apply --index B.diff'
+
+test_expect_failure 'apply binary diff (copy) -- should fail.' \
+ 'do_reset
+ git-apply C.diff'
+
+test_expect_failure 'apply binary diff (copy) -- should fail.' \
+ 'do_reset
+ git-apply --index C.diff'
+
+test_expect_failure 'apply binary diff without replacement -- should fail.' \
+ 'do_reset
+ git-apply BF.diff'
+
+test_expect_failure 'apply binary diff without replacement (copy) -- should fail.' \
+ 'do_reset
+ git-apply CF.diff'
+
+test_expect_success 'apply binary diff.' \
+ 'do_reset
+ git-apply --allow-binary-replacement --index BF.diff &&
+ test -z "$(git-diff --name-status binary)"'
+
+test_expect_success 'apply binary diff (copy).' \
+ 'do_reset
+ git-apply --allow-binary-replacement --index CF.diff &&
+ test -z "$(git-diff --name-status binary)"'
+
+test_done
diff --git a/t/t4109-apply-multifrag.sh b/t/t4109-apply-multifrag.sh
new file mode 100644
index 000000000..5988e1ae4
--- /dev/null
+++ b/t/t4109-apply-multifrag.sh
@@ -0,0 +1,176 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2005 Robert Fitzsimons
+#
+
+test_description='git-apply test patches with multiple fragments.
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat > patch1.patch <<\EOF
+diff --git a/main.c b/main.c
+new file mode 100644
+--- /dev/null
++++ b/main.c
+@@ -0,0 +1,23 @@
++#include <stdio.h>
++
++int func(int num);
++void print_int(int num);
++
++int main() {
++ int i;
++
++ for (i = 0; i < 10; i++) {
++ print_int(func(i));
++ }
++
++ return 0;
++}
++
++int func(int num) {
++ return num * num;
++}
++
++void print_int(int num) {
++ printf("%d", num);
++}
++
+EOF
+cat > patch2.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,7 +1,9 @@
++#include <stdlib.h>
+ #include <stdio.h>
+
+ int func(int num);
+ void print_int(int num);
++void print_ln();
+
+ int main() {
+ int i;
+@@ -10,6 +12,8 @@
+ print_int(func(i));
+ }
+
++ print_ln();
++
+ return 0;
+ }
+
+@@ -21,3 +25,7 @@
+ printf("%d", num);
+ }
+
++void print_ln() {
++ printf("\n");
++}
++
+EOF
+cat > patch3.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,9 +1,7 @@
+-#include <stdlib.h>
+ #include <stdio.h>
+
+ int func(int num);
+ void print_int(int num);
+-void print_ln();
+
+ int main() {
+ int i;
+@@ -12,8 +10,6 @@
+ print_int(func(i));
+ }
+
+- print_ln();
+-
+ return 0;
+ }
+
+@@ -25,7 +21,3 @@
+ printf("%d", num);
+ }
+
+-void print_ln() {
+- printf("\n");
+-}
+-
+EOF
+cat > patch4.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,13 +1,14 @@
+ #include <stdio.h>
+
+ int func(int num);
+-void print_int(int num);
++int func2(int num);
+
+ int main() {
+ int i;
+
+ for (i = 0; i < 10; i++) {
+- print_int(func(i));
++ printf("%d", func(i));
++ printf("%d", func3(i));
+ }
+
+ return 0;
+@@ -17,7 +18,7 @@
+ return num * num;
+ }
+
+-void print_int(int num) {
+- printf("%d", num);
++int func2(int num) {
++ return num * num * num;
+ }
+
+EOF
+
+test_expect_success "S = git-apply (1)" \
+ 'git-apply patch1.patch patch2.patch'
+mv main.c main.c.git
+
+test_expect_success "S = patch (1)" \
+ 'cat patch1.patch patch2.patch | patch -p1'
+
+test_expect_success "S = cmp (1)" \
+ 'cmp main.c.git main.c'
+
+rm -f main.c main.c.git
+
+test_expect_success "S = git-apply (2)" \
+ 'git-apply patch1.patch patch2.patch patch3.patch'
+mv main.c main.c.git
+
+test_expect_success "S = patch (2)" \
+ 'cat patch1.patch patch2.patch patch3.patch | patch -p1'
+
+test_expect_success "S = cmp (2)" \
+ 'cmp main.c.git main.c'
+
+rm -f main.c main.c.git
+
+test_expect_success "S = git-apply (3)" \
+ 'git-apply patch1.patch patch4.patch'
+mv main.c main.c.git
+
+test_expect_success "S = patch (3)" \
+ 'cat patch1.patch patch4.patch | patch -p1'
+
+test_expect_success "S = cmp (3)" \
+ 'cmp main.c.git main.c'
+
+test_done
+
diff --git a/t/t4110-apply-scan.sh b/t/t4110-apply-scan.sh
new file mode 100644
index 000000000..005f74481
--- /dev/null
+++ b/t/t4110-apply-scan.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+# Copyright (c) 2005 Robert Fitzsimons
+#
+
+test_description='git-apply test for patches which require scanning forwards and backwards.
+
+'
+. ./test-lib.sh
+
+# setup
+
+cat > patch1.patch <<\EOF
+diff --git a/new.txt b/new.txt
+new file mode 100644
+--- /dev/null
++++ b/new.txt
+@@ -0,0 +1,12 @@
++a1
++a11
++a111
++a1111
++b1
++b11
++b111
++b1111
++c1
++c11
++c111
++c1111
+EOF
+cat > patch2.patch <<\EOF
+diff --git a/new.txt b/new.txt
+--- a/new.txt
++++ b/new.txt
+@@ -1,7 +1,3 @@
+-a1
+-a11
+-a111
+-a1111
+ b1
+ b11
+ b111
+EOF
+cat > patch3.patch <<\EOF
+diff --git a/new.txt b/new.txt
+--- a/new.txt
++++ b/new.txt
+@@ -6,6 +6,10 @@
+ b11
+ b111
+ b1111
++b2
++b22
++b222
++b2222
+ c1
+ c11
+ c111
+EOF
+cat > patch4.patch <<\EOF
+diff --git a/new.txt b/new.txt
+--- a/new.txt
++++ b/new.txt
+@@ -1,3 +1,7 @@
++a1
++a11
++a111
++a1111
+ b1
+ b11
+ b111
+EOF
+cat > patch5.patch <<\EOF
+diff --git a/new.txt b/new.txt
+--- a/new.txt
++++ b/new.txt
+@@ -10,3 +10,7 @@
+ c11
+ c111
+ c1111
++c2
++c22
++c222
++c2222
+EOF
+
+test_expect_success "S = git-apply scan" \
+ 'git-apply patch1.patch patch2.patch patch3.patch patch4.patch patch5.patch'
+mv new.txt apply.txt
+
+test_expect_success "S = patch scan" \
+ 'cat patch1.patch patch2.patch patch3.patch patch4.patch patch5.patch | patch'
+mv new.txt patch.txt
+
+test_expect_success "S = cmp" \
+ 'cmp apply.txt patch.txt'
+
+test_done
+
diff --git a/t/t4112-apply-renames.sh b/t/t4112-apply-renames.sh
new file mode 100755
index 000000000..a06f6956d
--- /dev/null
+++ b/t/t4112-apply-renames.sh
@@ -0,0 +1,148 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-apply should not get confused with rename/copy.
+
+'
+
+. ./test-lib.sh
+
+# setup
+
+mkdir -p include/arch/x86_64/klibc klibc/arch/x86_64/include/klibc
+
+cat >include/arch/x86_64/klibc/archsetjmp.h <<\EOF
+/*
+ * arch/x86_64/include/klibc/archsetjmp.h
+ */
+
+#ifndef _KLIBC_ARCHSETJMP_H
+#define _KLIBC_ARCHSETJMP_H
+
+struct __jmp_buf {
+ unsigned long __rbx;
+ unsigned long __rsp;
+ unsigned long __rbp;
+ unsigned long __r12;
+ unsigned long __r13;
+ unsigned long __r14;
+ unsigned long __r15;
+ unsigned long __rip;
+};
+
+typedef struct __jmp_buf jmp_buf[1];
+
+#endif /* _SETJMP_H */
+EOF
+
+cat >klibc/arch/x86_64/include/klibc/archsetjmp.h <<\EOF
+/*
+ * arch/x86_64/include/klibc/archsetjmp.h
+ */
+
+#ifndef _KLIBC_ARCHSETJMP_H
+#define _KLIBC_ARCHSETJMP_H
+
+struct __jmp_buf {
+ unsigned long __rbx;
+ unsigned long __rsp;
+ unsigned long __rbp;
+ unsigned long __r12;
+ unsigned long __r13;
+ unsigned long __r14;
+ unsigned long __r15;
+ unsigned long __rip;
+};
+
+typedef struct __jmp_buf jmp_buf[1];
+
+#endif /* _SETJMP_H */
+EOF
+
+cat >patch <<\EOF
+diff --git a/klibc/arch/x86_64/include/klibc/archsetjmp.h b/include/arch/cris/klibc/archsetjmp.h
+similarity index 76%
+copy from klibc/arch/x86_64/include/klibc/archsetjmp.h
+copy to include/arch/cris/klibc/archsetjmp.h
+--- a/klibc/arch/x86_64/include/klibc/archsetjmp.h
++++ b/include/arch/cris/klibc/archsetjmp.h
+@@ -1,21 +1,24 @@
+ /*
+- * arch/x86_64/include/klibc/archsetjmp.h
++ * arch/cris/include/klibc/archsetjmp.h
+ */
+
+ #ifndef _KLIBC_ARCHSETJMP_H
+ #define _KLIBC_ARCHSETJMP_H
+
+ struct __jmp_buf {
+- unsigned long __rbx;
+- unsigned long __rsp;
+- unsigned long __rbp;
+- unsigned long __r12;
+- unsigned long __r13;
+- unsigned long __r14;
+- unsigned long __r15;
+- unsigned long __rip;
++ unsigned long __r0;
++ unsigned long __r1;
++ unsigned long __r2;
++ unsigned long __r3;
++ unsigned long __r4;
++ unsigned long __r5;
++ unsigned long __r6;
++ unsigned long __r7;
++ unsigned long __r8;
++ unsigned long __sp;
++ unsigned long __srp;
+ };
+
+ typedef struct __jmp_buf jmp_buf[1];
+
+-#endif /* _SETJMP_H */
++#endif /* _KLIBC_ARCHSETJMP_H */
+diff --git a/klibc/arch/x86_64/include/klibc/archsetjmp.h b/include/arch/m32r/klibc/archsetjmp.h
+similarity index 66%
+rename from klibc/arch/x86_64/include/klibc/archsetjmp.h
+rename to include/arch/m32r/klibc/archsetjmp.h
+--- a/klibc/arch/x86_64/include/klibc/archsetjmp.h
++++ b/include/arch/m32r/klibc/archsetjmp.h
+@@ -1,21 +1,21 @@
+ /*
+- * arch/x86_64/include/klibc/archsetjmp.h
++ * arch/m32r/include/klibc/archsetjmp.h
+ */
+
+ #ifndef _KLIBC_ARCHSETJMP_H
+ #define _KLIBC_ARCHSETJMP_H
+
+ struct __jmp_buf {
+- unsigned long __rbx;
+- unsigned long __rsp;
+- unsigned long __rbp;
++ unsigned long __r8;
++ unsigned long __r9;
++ unsigned long __r10;
++ unsigned long __r11;
+ unsigned long __r12;
+ unsigned long __r13;
+ unsigned long __r14;
+ unsigned long __r15;
+- unsigned long __rip;
+ };
+
+ typedef struct __jmp_buf jmp_buf[1];
+
+-#endif /* _SETJMP_H */
++#endif /* _KLIBC_ARCHSETJMP_H */
+EOF
+
+find include klibc -type f -print | xargs git-update-index --add --
+
+test_expect_success 'check rename/copy patch' 'git-apply --check patch'
+
+test_expect_success 'apply rename/copy patch' 'git-apply --index patch'
+
+test_done
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
new file mode 100755
index 000000000..adc5e937d
--- /dev/null
+++ b/t/t5000-tar-tree.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+#
+# Copyright (C) 2005 Rene Scharfe
+#
+
+test_description='git-tar-tree and git-get-tar-commit-id test
+
+This test covers the topics of file contents, commit date handling and
+commit id embedding:
+
+ The contents of the repository is compared to the extracted tar
+ archive. The repository contains simple text files, symlinks and a
+ binary file (/bin/sh). Only pathes shorter than 99 characters are
+ used.
+
+ git-tar-tree applies the commit date to every file in the archive it
+ creates. The test sets the commit date to a specific value and checks
+ if the tar archive contains that value.
+
+ When giving git-tar-tree a commit id (in contrast to a tree id) it
+ embeds this commit id into the tar archive as a comment. The test
+ checks the ability of git-get-tar-commit-id to figure it out from the
+ tar file.
+
+'
+
+. ./test-lib.sh
+TAR=${TAR:-tar}
+
+test_expect_success \
+ 'populate workdir' \
+ 'mkdir a b c &&
+ echo simple textfile >a/a &&
+ mkdir a/bin &&
+ cp /bin/sh a/bin &&
+ ln -s a a/l1 &&
+ (cd a && find .) | sort >a.lst'
+
+test_expect_success \
+ 'add files to repository' \
+ 'find a -type f | xargs git-update-index --add &&
+ find a -type l | xargs git-update-index --add &&
+ treeid=`git-write-tree` &&
+ echo $treeid >treeid &&
+ git-update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
+ git-commit-tree $treeid </dev/null)'
+
+test_expect_success \
+ 'git-tar-tree' \
+ 'git-tar-tree HEAD >b.tar'
+
+test_expect_success \
+ 'validate file modification time' \
+ 'TZ=GMT $TAR tvf b.tar a/a |
+ awk \{print\ \$4,\ \(length\(\$5\)\<7\)\ ?\ \$5\":00\"\ :\ \$5\} \
+ >b.mtime &&
+ echo "2005-05-27 22:00:00" >expected.mtime &&
+ diff expected.mtime b.mtime'
+
+test_expect_success \
+ 'git-get-tar-commit-id' \
+ 'git-get-tar-commit-id <b.tar >b.commitid &&
+ diff .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 &&
+ diff 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 &&
+ diff a.lst c.lst'
+
+test_expect_success \
+ 'validate file contents with prefix' \
+ 'diff -r a c/prefix/a'
+
+test_done
diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh
new file mode 100755
index 000000000..5b50536b5
--- /dev/null
+++ b/t/t5300-pack-object.sh
@@ -0,0 +1,186 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='git-pack-object
+
+'
+. ./test-lib.sh
+
+TRASH=`pwd`
+
+test_expect_success \
+ 'setup' \
+ 'rm -f .git/index*
+ for i in a b c
+ do
+ dd if=/dev/zero bs=4k count=1 | tr "\\0" $i >$i &&
+ git-update-index --add $i || return 1
+ done &&
+ cat c >d && echo foo >>d && git-update-index --add d &&
+ tree=`git-write-tree` &&
+ commit=`git-commit-tree $tree </dev/null` && {
+ echo $tree &&
+ echo $commit &&
+ git-ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
+ } >obj-list && {
+ git-diff-tree --root -p $commit &&
+ while read object
+ do
+ t=`git-cat-file -t $object` &&
+ git-cat-file $t $object || return 1
+ done <obj-list
+ } >expect'
+
+test_expect_success \
+ 'pack without delta' \
+ 'packname_1=$(git-pack-objects --window=0 test-1 <obj-list)'
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+ 'unpack without delta' \
+ "GIT_OBJECT_DIRECTORY=.git2/objects &&
+ export GIT_OBJECT_DIRECTORY &&
+ git-init-db &&
+ git-unpack-objects -n <test-1-${packname_1}.pack &&
+ git-unpack-objects <test-1-${packname_1}.pack"
+
+unset GIT_OBJECT_DIRECTORY
+cd "$TRASH/.git2"
+
+test_expect_success \
+ 'check unpack without delta' \
+ '(cd ../.git && find objects -type f -print) |
+ while read path
+ do
+ cmp $path ../.git/$path || {
+ echo $path differs.
+ return 1
+ }
+ done'
+cd "$TRASH"
+
+test_expect_success \
+ 'pack with delta' \
+ 'pwd &&
+ packname_2=$(git-pack-objects test-2 <obj-list)'
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+ 'unpack with delta' \
+ 'GIT_OBJECT_DIRECTORY=.git2/objects &&
+ export GIT_OBJECT_DIRECTORY &&
+ git-init-db &&
+ git-unpack-objects -n <test-2-${packname_2}.pack &&
+ git-unpack-objects <test-2-${packname_2}.pack'
+
+unset GIT_OBJECT_DIRECTORY
+cd "$TRASH/.git2"
+test_expect_success \
+ 'check unpack with delta' \
+ '(cd ../.git && find objects -type f -print) |
+ while read path
+ do
+ cmp $path ../.git/$path || {
+ echo $path differs.
+ return 1
+ }
+ done'
+cd "$TRASH"
+
+rm -fr .git2
+mkdir .git2
+
+test_expect_success \
+ 'use packed objects' \
+ 'GIT_OBJECT_DIRECTORY=.git2/objects &&
+ export GIT_OBJECT_DIRECTORY &&
+ git-init-db &&
+ cp test-1-${packname_1}.pack test-1-${packname_1}.idx .git2/objects/pack && {
+ git-diff-tree --root -p $commit &&
+ while read object
+ do
+ t=`git-cat-file -t $object` &&
+ git-cat-file $t $object || return 1
+ done <obj-list
+ } >current &&
+ diff expect current'
+
+
+test_expect_success \
+ 'use packed deltified objects' \
+ 'GIT_OBJECT_DIRECTORY=.git2/objects &&
+ export GIT_OBJECT_DIRECTORY &&
+ rm -f .git2/objects/pack/test-?.idx &&
+ cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
+ git-diff-tree --root -p $commit &&
+ while read object
+ do
+ t=`git-cat-file -t $object` &&
+ git-cat-file $t $object || return 1
+ done <obj-list
+ } >current &&
+ diff expect current'
+
+unset GIT_OBJECT_DIRECTORY
+
+test_expect_success \
+ 'verify pack' \
+ 'git-verify-pack test-1-${packname_1}.idx test-2-${packname_2}.idx'
+
+test_expect_success \
+ 'corrupt a pack and see if verify catches' \
+ 'cp test-1-${packname_1}.idx test-3.idx &&
+ cp test-2-${packname_2}.pack test-3.pack &&
+ if git-verify-pack test-3.idx
+ then false
+ else :;
+ fi &&
+
+ cp test-1-${packname_1}.pack test-3.pack &&
+ dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
+ if git-verify-pack test-3.idx
+ then false
+ else :;
+ fi &&
+
+ cp test-1-${packname_1}.pack test-3.pack &&
+ dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
+ if git-verify-pack test-3.idx
+ then false
+ else :;
+ fi &&
+
+ cp test-1-${packname_1}.pack test-3.pack &&
+ dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
+ if git-verify-pack test-3.idx
+ then false
+ else :;
+ fi &&
+
+ :'
+
+test_expect_success \
+ 'build pack index for an existing pack' \
+ 'cp test-1-${packname_1}.pack test-3.pack &&
+ git-index-pack -o tmp.idx test-3.pack &&
+ cmp tmp.idx test-1-${packname_1}.idx &&
+
+ git-index-pack test-3.pack &&
+ cmp test-3.idx test-1-${packname_1}.idx &&
+
+ cp test-2-${packname_2}.pack test-3.pack &&
+ git-index-pack -o tmp.idx test-2-${packname_2}.pack &&
+ cmp tmp.idx test-2-${packname_2}.idx &&
+
+ git-index-pack test-3.pack &&
+ cmp test-3.idx test-2-${packname_2}.idx &&
+
+ :'
+
+test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh
new file mode 100755
index 000000000..7fc3bd7d3
--- /dev/null
+++ b/t/t5400-send-pack.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='See why rewinding head breaks send-pack
+
+'
+. ./test-lib.sh
+
+touch cpio-test
+test_expect_success 'working cpio' 'echo cpio-test | cpio -o > /dev/null'
+
+cnt='1'
+test_expect_success setup '
+ tree=$(git-write-tree) &&
+ commit=$(echo "Commit #0" | git-commit-tree $tree) &&
+ zero=$commit &&
+ parent=$zero &&
+ for i in $cnt
+ do
+ sleep 1 &&
+ commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
+ parent=$commit || return 1
+ done &&
+ git-update-ref HEAD "$commit" &&
+ git-clone -l ./. victim &&
+ cd victim &&
+ git-log &&
+ cd .. &&
+ git-update-ref HEAD "$zero" &&
+ parent=$zero &&
+ for i in $cnt
+ do
+ sleep 1 &&
+ commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
+ parent=$commit || return 1
+ done &&
+ git-update-ref HEAD "$commit" &&
+ echo Rebase &&
+ git-log'
+
+test_expect_success \
+ 'pushing rewound head should not barf but require --force' '
+ # should not fail but refuse to update.
+ git-send-pack ./victim/.git/ master &&
+ if cmp victim/.git/refs/heads/master .git/refs/heads/master
+ then
+ # should have been left as it was!
+ false
+ else
+ true
+ fi &&
+ # this should update
+ git-send-pack --force ./victim/.git/ master &&
+ cmp victim/.git/refs/heads/master .git/refs/heads/master
+'
+
+test_done
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
new file mode 100644
index 000000000..0781bd287
--- /dev/null
+++ b/t/t5500-fetch-pack.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Testing multi_ack pack fetching
+
+'
+. ./test-lib.sh
+
+# Test fetch-pack/upload-pack pair.
+
+# Some convenience functions
+
+function show_count () {
+ commit_count=$(($commit_count+1))
+ printf " %d\r" $commit_count
+}
+
+function add () {
+ local name=$1
+ local text="$@"
+ local branch=${name:0:1}
+ local parents=""
+
+ shift
+ while test $1; do
+ parents="$parents -p $1"
+ shift
+ done
+
+ echo "$text" > test.txt
+ git-update-index --add test.txt
+ tree=$(git-write-tree)
+ # make sure timestamps are in correct order
+ sec=$(($sec+1))
+ commit=$(echo "$text" | GIT_AUTHOR_DATE=$sec \
+ git-commit-tree $tree $parents 2>>log2.txt)
+ export $name=$commit
+ echo $commit > .git/refs/heads/$branch
+ eval ${branch}TIP=$commit
+}
+
+function count_objects () {
+ ls .git/objects/??/* 2>>log2.txt | wc -l | tr -d " "
+}
+
+function test_expect_object_count () {
+ local message=$1
+ local count=$2
+
+ output="$(count_objects)"
+ test_expect_success \
+ "new object count $message" \
+ "test $count = $output"
+}
+
+function test_repack () {
+ local rep=$1
+
+ test_expect_success "repack && prune-packed in $rep" \
+ '(git-repack && git-prune-packed)2>>log.txt'
+}
+
+function pull_to_client () {
+ local number=$1
+ local heads=$2
+ local count=$3
+ local no_strict_count_check=$4
+
+ cd client
+ test_expect_success "$number pull" \
+ "git-fetch-pack -v .. $heads > log.txt 2>&1"
+ case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
+ case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
+ git-symbolic-ref HEAD refs/heads/${heads:0:1}
+ test_expect_success "fsck" 'git-fsck-objects --full > fsck.txt 2>&1'
+ test_expect_object_count "after $number pull" $count
+ pack_count=$(grep Packing log.txt|tr -dc "0-9")
+ test -z "$pack_count" && pack_count=0
+ if [ -z "$no_strict_count_check" ]; then
+ test_expect_success "minimal count" "test $count = $pack_count"
+ else
+ test $count != $pack_count && \
+ echo "WARNING: $pack_count objects transmitted, only $count of which were needed"
+ fi
+ cd ..
+}
+
+# Here begins the actual testing
+
+# A1 - ... - A20 - A21
+# \
+# B1 - B2 - .. - B70
+
+# client pulls A20, B1. Then tracks only B. Then pulls A.
+
+(
+ mkdir client &&
+ cd client &&
+ git-init-db 2>> log2.txt
+)
+
+add A1
+
+prev=1; cur=2; while [ $cur -le 10 ]; do
+ add A$cur $(eval echo \$A$prev)
+ prev=$cur
+ cur=$(($cur+1))
+done
+
+add B1 $A1
+
+echo $ATIP > .git/refs/heads/A
+echo $BTIP > .git/refs/heads/B
+git-symbolic-ref HEAD refs/heads/B
+
+pull_to_client 1st "B A" $((11*3))
+
+(cd client; test_repack client)
+
+add A11 $A10
+
+prev=1; cur=2; while [ $cur -le 65 ]; do
+ add B$cur $(eval echo \$B$prev)
+ prev=$cur
+ cur=$(($cur+1))
+done
+
+pull_to_client 2nd "B" $((64*3))
+
+(cd client; test_repack client)
+
+pull_to_client 3rd "A" $((1*3)) # old fails
+
+test_done
diff --git a/t/t5501-old-fetch-and-upload.sh b/t/t5501-old-fetch-and-upload.sh
new file mode 100755
index 000000000..ada513032
--- /dev/null
+++ b/t/t5501-old-fetch-and-upload.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+# Test that the current fetch-pack/upload-pack plays nicely with
+# an old counterpart
+
+cd $(dirname $0) || exit 1
+
+tmp=$(mktemp /tmp/tmp-XXXXXXXX)
+
+retval=0
+
+if [ -z "$1" ]; then
+ list="fetch upload"
+else
+ list="$@"
+fi
+
+for i in $list; do
+ case "$i" in
+ fetch) pgm="old-git-fetch-pack"; replace="$pgm";;
+ upload) pgm="old-git-upload-pack"; replace="git-fetch-pack --exec=$pgm";;
+ both) pgm="old-git-upload-pack"; replace="old-git-fetch-pack --exec=$pgm";;
+ esac
+
+ if which $pgm 2>/dev/null; then
+ echo "Testing with $pgm"
+ sed -e "s/git-fetch-pack/$replace/g" \
+ -e "s/# old fails/warn/" < t5500-fetch-pack.sh > $tmp
+
+ sh $tmp || retval=$?
+ rm $tmp
+
+ test $retval != 0 && exit $retval
+ else
+ echo "Skipping test for $i, since I cannot find $pgm"
+ fi
+done
+
+exit 0
+
diff --git a/t/t6000lib.sh b/t/t6000lib.sh
new file mode 100755
index 000000000..01f796e9c
--- /dev/null
+++ b/t/t6000lib.sh
@@ -0,0 +1,109 @@
+[ -d .git/refs/tags ] || mkdir -p .git/refs/tags
+
+:> sed.script
+
+# Answer the sha1 has associated with the tag. The tag must exist in .git or .git/refs/tags
+tag()
+{
+ _tag=$1
+ [ -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()
+{
+ _text=$1
+ _tree=$2
+ shift 2
+ 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()
+{
+ _tag=$1
+ [ -n "$_tag" ] || error "usage: save_tag tag commit-args ..."
+ shift 1
+ "$@" >.git/refs/tags/$_tag
+
+ 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()
+{
+ 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()
+{
+ _author=$1
+ shift 1
+ _save=$GIT_AUTHOR_EMAIL
+
+ export GIT_AUTHOR_EMAIL="$_author"
+ "$@"
+ export GIT_AUTHOR_EMAIL="$_save"
+}
+
+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 "$@"
+}
+
+# Execute a command and suppress any error output.
+hide_error()
+{
+ "$@" 2>/dev/null
+}
+
+check_output()
+{
+ _name=$1
+ shift 1
+ if eval "$*" | entag > $_name.actual
+ then
+ diff $_name.expected $_name.actual
+ else
+ 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()
+{
+ tr "'" '-' | tr '~`!@#$%^&*()_+={}[]|\;:"<>,/? ' '-' | tr -s '-' | tr '[A-Z]' '[a-z]' | sed "s/^-*//;s/-*\$//"
+}
+
+
+# Execute the test described by the first argument, by eval'ing
+# command line specified in the 2nd argument. Check the status code
+# is zero and that the output matches the stream read from
+# stdin.
+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_expect_success "$_description" "check_output $_name \"$_test\""
+}
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
new file mode 100755
index 000000000..8ec9ebb98
--- /dev/null
+++ b/t/t6001-rev-list-merge-order.sh
@@ -0,0 +1,462 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+
+test_description='Tests git-rev-list --merge-order functionality'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+if git-rev-list --merge-order 2>&1 | grep 'OpenSSL not linked' >/dev/null
+then
+ test_expect_success 'skipping merge-order test' :
+ test_done
+ exit
+fi
+
+# test-case specific test function
+check_adjacency()
+{
+ read previous
+ echo "= $previous"
+ while read next
+ do
+ if ! (git-cat-file commit $previous | grep "^parent $next" >/dev/null)
+ then
+ echo "^ $next"
+ else
+ echo "| $next"
+ fi
+ previous=$next
+ done
+}
+
+list_duplicates()
+{
+ "$@" | sort | uniq -d
+}
+
+grep_stderr()
+{
+ args=$1
+ shift 1
+ "$@" 2>&1 | grep "$args"
+}
+
+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 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 "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
+
+
+#
+# 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
+save_tag e3 as_author e@example.com unique_commit e3 tree -p e2
+save_tag f2 as_author f@example.com unique_commit f2 tree -p f1
+save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2
+save_tag e5 as_author e@example.com unique_commit e5 tree -p e4
+save_tag f3 as_author f@example.com unique_commit f3 tree -p f2
+save_tag f4 as_author f@example.com unique_commit f4 tree -p f3
+save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4
+save_tag f5 as_author f@example.com unique_commit f5 tree -p f4
+save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6
+save_tag e7 as_author e@example.com unique_commit e7 tree -p e6
+save_tag e8 as_author e@example.com unique_commit e8 tree -p e7
+save_tag e9 as_author e@example.com unique_commit e9 tree -p e8
+save_tag f7 as_author f@example.com unique_commit f7 tree -p f6
+save_tag f8 as_author f@example.com unique_commit f8 tree -p f7
+save_tag f9 as_author f@example.com unique_commit f9 tree -p f8
+save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8
+
+hide_error save_tag g0 unique_commit g0 tree
+save_tag g1 unique_commit g1 tree -p g0
+save_tag h1 unique_commit g2 tree -p g0
+save_tag g2 unique_commit g3 tree -p g1 -p h1
+save_tag h2 unique_commit g4 tree -p g2
+save_tag g3 unique_commit g5 tree -p g2
+save_tag g4 unique_commit g6 tree -p g3 -p h2
+
+git-update-ref HEAD $(tag l5)
+
+test_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
+19
+EOF
+
+normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
+merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
+test_expect_success '--merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
+test_output_expect_success 'simple merge order' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| b3
+| b2
+| b1
+^ a3
+| a2
+| a1
+= a0
+| l2
+| l1
+| l0
+= root
+EOF
+
+test_output_expect_success 'two diamonds merge order (g6)' 'git-rev-list --merge-order --show-breaks g4' <<EOF
+= g4
+| h2
+^ g3
+= g2
+| h1
+^ g1
+= g0
+EOF
+
+test_output_expect_success 'multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF
+c3
+c2
+c1
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF
+c3
+c2
+c1
+b3
+b2
+b1
+a3
+a2
+EOF
+
+test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF
+c3
+c2
+c1
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
+
+test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
+
+test_output_expect_success 'duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+EOF
+
+test_output_expect_success 'prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF
+a4
+b4
+b3
+a3
+a2
+a1
+EOF
+
+test_output_expect_success "head has no parent" 'git-rev-list --merge-order --show-breaks root' <<EOF
+= root
+EOF
+
+test_output_expect_success "two nodes - one head, one base" 'git-rev-list --merge-order --show-breaks l0' <<EOF
+= l0
+= root
+EOF
+
+test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --merge-order --show-breaks l1' <<EOF
+= l1
+| l0
+= root
+EOF
+
+test_output_expect_success "linear prune l2 ^root" 'git-rev-list --merge-order --show-breaks l2 ^root' <<EOF
+^ l2
+| l1
+| l0
+EOF
+
+test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --merge-order --show-breaks l2 ^l0' <<EOF
+^ l2
+| l1
+EOF
+
+test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --merge-order --show-breaks l2 ^l1' <<EOF
+^ l2
+EOF
+
+test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --merge-order --show-breaks l5 ^a4' <<EOF
+^ l5
+| l4
+| l3
+EOF
+
+test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --merge-order --show-breaks l5 ^l3' <<EOF
+^ l5
+| l4
+EOF
+
+test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --merge-order --show-breaks l5 ^l4' <<EOF
+^ l5
+EOF
+
+test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-order --show-breaks --max-count=10 l5' <<EOF
+= l5
+| l4
+| l3
+= a4
+| c3
+| c2
+| c1
+^ b4
+| b3
+| b2
+EOF
+
+test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5' <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+c2
+b3
+EOF
+
+test_output_expect_success '--max-age=c3, no --merge-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+EOF
+
+test_output_expect_success '--max-age=c3, --merge-order' "git-rev-list --merge-order --max-age=$(commit_date c3) l5" <<EOF
+l5
+l4
+l3
+a4
+c3
+b4
+a3
+a2
+EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, --merge-order' "list_duplicates git-rev-list --merge-order a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, --merge-order' "list_duplicates git-rev-list --merge-order c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, no --merge-order' "list_duplicates git-rev-list a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, no --merge-order' "list_duplicates git-rev-list c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
+EOF
+
+test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
+EOF
+
+test_expect_success "head ^head --merge-order" 'git-rev-list --merge-order --show-breaks a3 ^a3' <<EOF
+EOF
+
+#
+# can't test this now - duplicate parents can't be created
+#
+#test_output_expect_success 'duplicate parents' 'git-rev-list --parents --merge-order --show-breaks m3' <<EOF
+#= m3 c3 a4 c3
+#| a4 c3 b4 a3
+#| b4 a3 b3
+#| b3 b2
+#^ a3 a2
+#| a2 a1
+#| a1 a0
+#^ c3 c2
+#| c2 b2 c1
+#| b2 b1
+#^ c1 b1
+#| b1 a0
+#= a0 l2
+#| l2 l1
+#| l1 l0
+#| l0 root
+#= root
+#EOF
+
+test_expect_success "head ^head no --merge-order" 'git-rev-list a3 ^a3' <<EOF
+EOF
+
+test_output_expect_success 'simple merge order (l5r1)' 'git-rev-list --merge-order --show-breaks l5r1' <<EOF
+= l5r1
+| r1
+| r0
+| alt_root
+^ l5
+| l4
+| l3
+| a4
+| c3
+| c2
+| c1
+^ b4
+| b3
+| b2
+| b1
+^ a3
+| a2
+| a1
+| a0
+| l2
+| l1
+| l0
+= root
+EOF
+
+test_output_expect_success 'simple merge order (r1l5)' 'git-rev-list --merge-order --show-breaks r1l5' <<EOF
+= r1l5
+| l5
+| l4
+| l3
+| a4
+| c3
+| c2
+| c1
+^ b4
+| b3
+| b2
+| b1
+^ a3
+| a2
+| a1
+| a0
+| l2
+| l1
+| l0
+| root
+^ r1
+| r0
+= alt_root
+EOF
+
+test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --merge-order" <<EOF
+a3
+a2
+a1
+EOF
+
+test_output_expect_success "--merge-order a4 l3" "git-rev-list --merge-order a4 l3" <<EOF
+l3
+a4
+c3
+c2
+c1
+b4
+b3
+b2
+b1
+a3
+a2
+a1
+a0
+l2
+l1
+l0
+root
+EOF
+
+#
+#
+
+test_done
diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh
new file mode 100755
index 000000000..693de9b32
--- /dev/null
+++ b/t/t6002-rev-list-bisect.sh
@@ -0,0 +1,238 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+test_description='Tests git-rev-list --bisect functionality'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+# usage: test_bisection max-diff bisect-option head ^prune...
+#
+# e.g. test_bisection 1 --bisect l1 ^l0
+#
+test_bisection_diff()
+{
+ _max_diff=$1
+ _bisect_option=$2
+ shift 2
+ _bisection=$(git-rev-list $_bisect_option "$@")
+ _list_size=$(git-rev-list "$@" | wc -l)
+ _head=$1
+ shift 1
+ _bisection_size=$(git-rev-list $_bisection "$@" | wc -l)
+ [ -n "$_list_size" -a -n "$_bisection_size" ] ||
+ error "test_bisection_diff failed"
+
+ # Test if bisection size is close to half of list size within
+ # tolerance.
+ #
+ _bisect_err=`expr $_list_size - $_bisection_size \* 2`
+ test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err`
+ _bisect_err=`expr $_bisect_err / 2` ; # floor
+
+ test_expect_success \
+ "bisection diff $_bisect_option $_head $* <= $_max_diff" \
+ 'test $_bisect_err -le $_max_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
+git-update-ref HEAD $(tag l5)
+
+
+# E
+# / \
+# e1 |
+# | |
+# e2 |
+# | |
+# e3 |
+# | |
+# e4 |
+# | |
+# | f1
+# | |
+# | f2
+# | |
+# | f3
+# | |
+# | f4
+# | |
+# e5 |
+# | |
+# e6 |
+# | |
+# e7 |
+# | |
+# e8 |
+# \ /
+# 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
+
+test_sequence()
+{
+ _bisect_option=$1
+
+ test_bisection_diff 0 $_bisect_option l0 ^root
+ test_bisection_diff 0 $_bisect_option l1 ^root
+ test_bisection_diff 0 $_bisect_option l2 ^root
+ test_bisection_diff 0 $_bisect_option a0 ^root
+ test_bisection_diff 0 $_bisect_option a1 ^root
+ test_bisection_diff 0 $_bisect_option a2 ^root
+ test_bisection_diff 0 $_bisect_option a3 ^root
+ test_bisection_diff 0 $_bisect_option b1 ^root
+ test_bisection_diff 0 $_bisect_option b2 ^root
+ test_bisection_diff 0 $_bisect_option b3 ^root
+ test_bisection_diff 0 $_bisect_option c1 ^root
+ test_bisection_diff 0 $_bisect_option c2 ^root
+ test_bisection_diff 0 $_bisect_option c3 ^root
+ test_bisection_diff 0 $_bisect_option E ^F
+ test_bisection_diff 0 $_bisect_option e1 ^F
+ test_bisection_diff 0 $_bisect_option e2 ^F
+ test_bisection_diff 0 $_bisect_option e3 ^F
+ test_bisection_diff 0 $_bisect_option e4 ^F
+ test_bisection_diff 0 $_bisect_option e5 ^F
+ test_bisection_diff 0 $_bisect_option e6 ^F
+ test_bisection_diff 0 $_bisect_option e7 ^F
+ test_bisection_diff 0 $_bisect_option f1 ^F
+ test_bisection_diff 0 $_bisect_option f2 ^F
+ test_bisection_diff 0 $_bisect_option f3 ^F
+ test_bisection_diff 0 $_bisect_option f4 ^F
+ test_bisection_diff 0 $_bisect_option E ^F
+
+ test_bisection_diff 1 $_bisect_option V ^U
+ test_bisection_diff 0 $_bisect_option V ^U ^u1 ^u2 ^u3
+ test_bisection_diff 0 $_bisect_option u1 ^U
+ test_bisection_diff 0 $_bisect_option u2 ^U
+ test_bisection_diff 0 $_bisect_option u3 ^U
+ test_bisection_diff 0 $_bisect_option u4 ^U
+ test_bisection_diff 0 $_bisect_option u5 ^U
+
+#
+# the following illustrate's Linus' binary bug blatt idea.
+#
+# assume the bug is actually at l3, but you don't know that - all you know is that l3 is broken
+# and it wasn't broken before
+#
+# keep bisecting the list, advancing the "bad" head and accumulating "good" heads until
+# the bisection point is the head - this is the bad point.
+#
+
+test_output_expect_success "--bisect l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF
+c3
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^root ^c3" 'git-rev-list $_bisect_option l5 ^root ^c3' <<EOF
+b4
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l5 ^c3 ^b4' <<EOF
+l3
+EOF
+
+test_output_expect_success "$_bisect_option l3 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l3 ^root ^c3 ^b4' <<EOF
+a4
+EOF
+
+test_output_expect_success "$_bisect_option l5 ^b3 ^a3 ^b4 ^a4" 'git-rev-list $_bisect_option l3 ^b3 ^a3 ^a4' <<EOF
+l3
+EOF
+
+#
+# if l3 is bad, then l4 is bad too - so advance the bad pointer by making b4 the known bad head
+#
+
+test_output_expect_success "$_bisect_option l4 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l4 ^a2 ^a3 ^a4' <<EOF
+l3
+EOF
+
+test_output_expect_success "$_bisect_option l3 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l3 ^a2 ^a3 ^a4' <<EOF
+l3
+EOF
+
+# found!
+
+#
+# as another example, let's consider a4 to be the bad head, in which case
+#
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+c2
+EOF
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2' <<EOF
+c3
+EOF
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3' <<EOF
+a4
+EOF
+
+# found!
+
+#
+# or consider c3 to be the bad head
+#
+
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+c2
+EOF
+
+test_output_expect_success "$_bisect_option c3 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option c3 ^a2 ^a3 ^b4 ^c2' <<EOF
+c3
+EOF
+
+# found!
+
+}
+
+test_sequence "--bisect"
+
+#
+#
+test_done
diff --git a/t/t6003-rev-list-topo-order.sh b/t/t6003-rev-list-topo-order.sh
new file mode 100755
index 000000000..98f9a1e67
--- /dev/null
+++ b/t/t6003-rev-list-topo-order.sh
@@ -0,0 +1,408 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Jon Seymour
+#
+
+test_description='Tests git-rev-list --topo-order functionality'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+list_duplicates()
+{
+ "$@" | sort | uniq -d
+}
+
+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
+
+
+#
+# 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
+save_tag e3 as_author e@example.com unique_commit e3 tree -p e2
+save_tag f2 as_author f@example.com unique_commit f2 tree -p f1
+save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2
+save_tag e5 as_author e@example.com unique_commit e5 tree -p e4
+save_tag f3 as_author f@example.com unique_commit f3 tree -p f2
+save_tag f4 as_author f@example.com unique_commit f4 tree -p f3
+save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4
+save_tag f5 as_author f@example.com unique_commit f5 tree -p f4
+save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6
+save_tag e7 as_author e@example.com unique_commit e7 tree -p e6
+save_tag e8 as_author e@example.com unique_commit e8 tree -p e7
+save_tag e9 as_author e@example.com unique_commit e9 tree -p e8
+save_tag f7 as_author f@example.com unique_commit f7 tree -p f6
+save_tag f8 as_author f@example.com unique_commit f8 tree -p f7
+save_tag f9 as_author f@example.com unique_commit f9 tree -p f8
+save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8
+
+hide_error save_tag g0 unique_commit g0 tree
+save_tag g1 unique_commit g1 tree -p g0
+save_tag h1 unique_commit g2 tree -p g0
+save_tag g2 unique_commit g3 tree -p g1 -p h1
+save_tag h2 unique_commit g4 tree -p g2
+save_tag g3 unique_commit g5 tree -p g2
+save_tag g4 unique_commit g6 tree -p g3 -p h2
+
+git-update-ref HEAD $(tag l5)
+
+test_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -s " "' <<EOF
+19
+EOF
+
+test_output_expect_success 'simple topo order' 'git-rev-list --topo-order HEAD' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'two diamonds topo order (g6)' 'git-rev-list --topo-order g4' <<EOF
+g4
+h2
+g3
+g2
+h1
+g1
+g0
+EOF
+
+test_output_expect_success 'multiple heads' 'git-rev-list --topo-order a3 b3 c3' <<EOF
+a3
+a2
+a1
+c3
+c2
+c1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --topo-order a3 b3 c3 ^a1' <<EOF
+a3
+a2
+c3
+c2
+c1
+b3
+b2
+b1
+EOF
+
+test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --topo-order a3 b3 c3 ^l1' <<EOF
+a3
+a2
+a1
+c3
+c2
+c1
+b3
+b2
+b1
+a0
+l2
+EOF
+
+test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --topo-order l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+EOF
+
+test_output_expect_success 'duplicated head arguments' 'git-rev-list --topo-order l5 l5 ^l1' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+EOF
+
+test_output_expect_success 'prune near topo' 'git-rev-list --topo-order a4 ^c3' <<EOF
+a4
+b4
+a3
+a2
+a1
+b3
+EOF
+
+test_output_expect_success "head has no parent" 'git-rev-list --topo-order root' <<EOF
+root
+EOF
+
+test_output_expect_success "two nodes - one head, one base" 'git-rev-list --topo-order l0' <<EOF
+l0
+root
+EOF
+
+test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --topo-order l1' <<EOF
+l1
+l0
+root
+EOF
+
+test_output_expect_success "linear prune l2 ^root" 'git-rev-list --topo-order l2 ^root' <<EOF
+l2
+l1
+l0
+EOF
+
+test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --topo-order l2 ^l0' <<EOF
+l2
+l1
+EOF
+
+test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --topo-order l2 ^l1' <<EOF
+l2
+EOF
+
+test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --topo-order l5 ^a4' <<EOF
+l5
+l4
+l3
+EOF
+
+test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --topo-order l5 ^l3' <<EOF
+l5
+l4
+EOF
+
+test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --topo-order l5 ^l4' <<EOF
+l5
+EOF
+
+test_output_expect_success "max-count 10 - topo order" 'git-rev-list --topo-order --max-count=10 l5' <<EOF
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+EOF
+
+test_output_expect_success "max-count 10 - non topo order" 'git-rev-list --max-count=10 l5' <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+c2
+b3
+EOF
+
+test_output_expect_success '--max-age=c3, no --topo-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
+l5
+l4
+l3
+a4
+b4
+a3
+a2
+c3
+EOF
+
+#
+# this test fails on --topo-order - a fix is required
+#
+#test_output_expect_success '--max-age=c3, --topo-order' "git-rev-list --topo-order --max-age=$(commit_date c3) l5" <<EOF
+#l5
+#l4
+#l3
+#a4
+#c3
+#b4
+#a3
+#a2
+#EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, --topo-order' "list_duplicates git-rev-list --topo-order a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, --topo-order' "list_duplicates git-rev-list --topo-order c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another a4, c3, no --topo-order' "list_duplicates git-rev-list a4 c3" <<EOF
+EOF
+
+test_output_expect_success 'one specified head reachable from another c3, a4, no --topo-order' "list_duplicates git-rev-list c3 a4" <<EOF
+EOF
+
+test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
+EOF
+
+test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
+EOF
+
+test_expect_success "head ^head --topo-order" 'git-rev-list --topo-order a3 ^a3' <<EOF
+EOF
+
+test_expect_success "head ^head no --topo-order" 'git-rev-list a3 ^a3' <<EOF
+EOF
+
+test_output_expect_success 'simple topo order (l5r1)' 'git-rev-list --topo-order l5r1' <<EOF
+l5r1
+r1
+r0
+alt_root
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+test_output_expect_success 'simple topo order (r1l5)' 'git-rev-list --topo-order r1l5' <<EOF
+r1l5
+l5
+l4
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+r1
+r0
+alt_root
+EOF
+
+test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --topo-order" <<EOF
+a3
+a2
+a1
+EOF
+
+test_output_expect_success "--topo-order a4 l3" "git-rev-list --topo-order a4 l3" <<EOF
+l3
+a4
+c3
+c2
+c1
+b4
+a3
+a2
+a1
+b3
+b2
+b1
+a0
+l2
+l1
+l0
+root
+EOF
+
+#
+#
+
+test_done
diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh
new file mode 100755
index 000000000..c3a9680e2
--- /dev/null
+++ b/t/t6010-merge-base.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+test_description='Merge base computation.
+'
+
+. ./test-lib.sh
+
+T=$(git-write-tree)
+
+M=1130000000
+Z=+0000
+
+export GIT_COMMITTER_EMAIL=git@comm.iter.xz
+export GIT_COMMITTER_NAME='C O Mmiter'
+export GIT_AUTHOR_NAME='A U Thor'
+export GIT_AUTHOR_EMAIL=git@au.thor.xz
+
+doit() {
+ OFFSET=$1; shift
+ NAME=$1; shift
+ PARENTS=
+ for P
+ do
+ PARENTS="${PARENTS}-p $P "
+ done
+ GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z"
+ GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE
+ export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+ commit=$(echo $NAME | git-commit-tree $T $PARENTS)
+ echo $commit >.git/refs/tags/$NAME
+ echo $commit
+}
+
+# Setup...
+E=$(doit 5 E)
+D=$(doit 4 D $E)
+F=$(doit 6 F $E)
+C=$(doit 3 C $D)
+B=$(doit 2 B $C)
+A=$(doit 1 A $B)
+G=$(doit 7 G $B $E)
+H=$(doit 8 H $A $F)
+
+test_expect_success 'compute merge-base (single)' \
+ 'MB=$(git-merge-base G H) &&
+ expr "$(git-name-rev "$MB")" : "[0-9a-f]* B"'
+
+test_expect_success 'compute merge-base (all)' \
+ 'MB=$(git-merge-base --all G H) &&
+ expr "$(git-name-rev "$MB")" : "[0-9a-f]* B"'
+
+test_expect_success 'compute merge-base with show-branch' \
+ 'MB=$(git-show-branch --merge-base G H) &&
+ expr "$(git-name-rev "$MB")" : "[0-9a-f]* B"'
+
+test_done
diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh
new file mode 100644
index 000000000..1beab7197
--- /dev/null
+++ b/t/t6101-rev-parse-parents.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Johannes Schindelin
+#
+
+test_description='Test git-rev-parse with different parent options'
+
+. ./test-lib.sh
+. ../t6000lib.sh # t6xxx specific functions
+
+date >path0
+git-update-index --add path0
+save_tag tree git-write-tree
+hide_error save_tag start unique_commit "start" tree
+save_tag second unique_commit "second" tree -p start
+hide_error save_tag start2 unique_commit "start2" tree
+save_tag two_parents unique_commit "next" tree -p second -p start2
+save_tag final unique_commit "final" tree -p two_parents
+
+test_expect_success 'start is valid' 'git-rev-parse start | grep "^[0-9a-f]\{40\}$"'
+test_expect_success 'start^0' "test $(cat .git/refs/tags/start) = $(git-rev-parse start^0)"
+test_expect_success 'start^1 not valid' "test $(git-rev-parse start^1) = start^1"
+test_expect_success 'second^1 = second^' "test $(git-rev-parse second^1) = $(git-rev-parse second^)"
+test_expect_success 'final^1^1^1' "test $(git-rev-parse start) = $(git-rev-parse final^1^1^1)"
+test_expect_success 'final^1^1^1 = final^^^' "test $(git-rev-parse final^1^1^1) = $(git-rev-parse final^^^)"
+test_expect_success 'final^1^2' "test $(git-rev-parse start2) = $(git-rev-parse final^1^2)"
+test_expect_success 'final^1^2 != final^1^1' "test $(git-rev-parse final^1^2) != $(git-rev-parse final^1^1)"
+test_expect_success 'final^1^3 not valid' "test $(git-rev-parse final^1^3) = final^1^3"
+test_expect_failure '--verify start2^1' 'git-rev-parse --verify start2^1'
+test_expect_success '--verify start2^0' 'git-rev-parse --verify start2^0'
+
+test_done
+
diff --git a/t/test-lib.sh b/t/test-lib.sh
new file mode 100755
index 000000000..e654155a2
--- /dev/null
+++ b/t/test-lib.sh
@@ -0,0 +1,173 @@
+#!/bin/sh
+#
+# Copyright (c) 2005 Junio C Hamano
+#
+
+# For repeatability, reset the environment to known value.
+LANG=C
+LC_ALL=C
+PAGER=cat
+TZ=UTC
+export LANG LC_ALL PAGER TZ
+unset AUTHOR_DATE
+unset AUTHOR_EMAIL
+unset AUTHOR_NAME
+unset COMMIT_AUTHOR_EMAIL
+unset COMMIT_AUTHOR_NAME
+unset GIT_ALTERNATE_OBJECT_DIRECTORIES
+unset GIT_AUTHOR_DATE
+unset GIT_AUTHOR_EMAIL
+unset GIT_AUTHOR_NAME
+unset GIT_COMMITTER_EMAIL
+unset GIT_COMMITTER_NAME
+unset GIT_DIFF_OPTS
+unset GIT_DIR
+unset GIT_EXTERNAL_DIFF
+unset GIT_INDEX_FILE
+unset GIT_OBJECT_DIRECTORY
+unset SHA1_FILE_DIRECTORIES
+unset SHA1_FILE_DIRECTORY
+
+# Each test should start with something like this, after copyright notices:
+#
+# test_description='Description of this test...
+# This test checks if command xyzzy does the right thing...
+# '
+# . ./test-lib.sh
+
+error () {
+ echo "* error: $*"
+ trap - exit
+ exit 1
+}
+
+say () {
+ echo "* $*"
+}
+
+test "${test_description}" != "" ||
+error "Test script did not set test_description."
+
+while test "$#" -ne 0
+do
+ case "$1" in
+ -d|--d|--de|--deb|--debu|--debug)
+ debug=t; shift ;;
+ -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate)
+ immediate=t; shift ;;
+ -h|--h|--he|--hel|--help)
+ echo "$test_description"
+ exit 0 ;;
+ -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+ verbose=t; shift ;;
+ *)
+ break ;;
+ esac
+done
+
+exec 5>&1
+if test "$verbose" = "t"
+then
+ exec 4>&2 3>&1
+else
+ exec 4>/dev/null 3>/dev/null
+fi
+
+test_failure=0
+test_count=0
+
+trap 'echo >&5 "FATAL: Unexpected exit with code $?"; exit 1' exit
+
+
+# You are not expected to call test_ok_ and test_failure_ directly, use
+# the text_expect_* functions instead.
+
+test_ok_ () {
+ test_count=$(expr "$test_count" + 1)
+ say " ok $test_count: $@"
+}
+
+test_failure_ () {
+ test_count=$(expr "$test_count" + 1)
+ test_failure=$(expr "$test_failure" + 1);
+ say "FAIL $test_count: $1"
+ shift
+ echo "$@" | sed -e 's/^/ /'
+ test "$immediate" = "" || { trap - exit; exit 1; }
+}
+
+
+test_debug () {
+ test "$debug" = "" || eval "$1"
+}
+
+test_run_ () {
+ eval >&3 2>&4 "$1"
+ eval_ret="$?"
+ return 0
+}
+
+test_expect_failure () {
+ test "$#" = 2 ||
+ error "bug in the test script: not 2 parameters to test-expect-failure"
+ say >&3 "expecting failure: $2"
+ test_run_ "$2"
+ if [ "$?" = 0 -a "$eval_ret" != 0 ]
+ then
+ test_ok_ "$1"
+ else
+ test_failure_ "$@"
+ fi
+}
+
+test_expect_success () {
+ test "$#" = 2 ||
+ error "bug in the test script: not 2 parameters to test-expect-success"
+ say >&3 "expecting success: $2"
+ test_run_ "$2"
+ if [ "$?" = 0 -a "$eval_ret" = 0 ]
+ then
+ test_ok_ "$1"
+ else
+ test_failure_ "$@"
+ fi
+}
+
+test_done () {
+ trap - exit
+ case "$test_failure" in
+ 0)
+ # We could:
+ # cd .. && rm -fr trash
+ # but that means we forbid any tests that use their own
+ # subdirectory from calling test_done without coming back
+ # to where they started from.
+ # The Makefile provided will clean this test area so
+ # we will leave things as they are.
+
+ say "passed all $test_count test(s)"
+ exit 0 ;;
+
+ *)
+ say "failed $test_failure among $test_count test(s)"
+ exit 1 ;;
+
+ esac
+}
+
+# Test the binaries we have just built. The tests are kept in
+# t/ subdirectory and are run in trash subdirectory.
+PATH=$(pwd)/..:$PATH
+GIT_EXEC_PATH=$(pwd)/..
+export GIT_EXEC_PATH
+
+# Test repository
+test=trash
+rm -fr "$test"
+mkdir "$test"
+cd "$test"
+git-init-db --template=../../templates/blt/ 2>/dev/null ||
+error "cannot run git-init-db"
+
+mv .git/hooks .git/hooks-disabled
+
diff --git a/tag.c b/tag.c
new file mode 100644
index 000000000..61ac434d6
--- /dev/null
+++ b/tag.c
@@ -0,0 +1,108 @@
+#include "tag.h"
+#include "cache.h"
+
+const char *tag_type = "tag";
+
+struct object *deref_tag(struct object *o, const char *warn, int warnlen)
+{
+ while (o && o->type == tag_type)
+ o = parse_object(((struct tag *)o)->tagged->sha1);
+ if (!o && warn) {
+ if (!warnlen)
+ warnlen = strlen(warn);
+ error("missing object referenced by '%.*s'", warnlen, warn);
+ }
+ return o;
+}
+
+struct tag *lookup_tag(const unsigned char *sha1)
+{
+ struct object *obj = lookup_object(sha1);
+ if (!obj) {
+ struct tag *ret = xmalloc(sizeof(struct tag));
+ memset(ret, 0, sizeof(struct tag));
+ created_object(sha1, &ret->object);
+ ret->object.type = tag_type;
+ return ret;
+ }
+ if (!obj->type)
+ obj->type = tag_type;
+ if (obj->type != tag_type) {
+ error("Object %s is a %s, not a tree",
+ sha1_to_hex(sha1), obj->type);
+ return NULL;
+ }
+ return (struct tag *) obj;
+}
+
+int parse_tag_buffer(struct tag *item, void *data, unsigned long size)
+{
+ int typelen, taglen;
+ unsigned char object[20];
+ const char *type_line, *tag_line, *sig_line;
+ char type[20];
+
+ if (item->object.parsed)
+ return 0;
+ item->object.parsed = 1;
+
+ if (size < 64)
+ return -1;
+ if (memcmp("object ", data, 7) || get_sha1_hex(data + 7, object))
+ return -1;
+
+ type_line = data + 48;
+ if (memcmp("\ntype ", type_line-1, 6))
+ return -1;
+
+ tag_line = strchr(type_line, '\n');
+ if (!tag_line || memcmp("tag ", ++tag_line, 4))
+ return -1;
+
+ sig_line = strchr(tag_line, '\n');
+ if (!sig_line)
+ return -1;
+ sig_line++;
+
+ typelen = tag_line - type_line - strlen("type \n");
+ if (typelen >= 20)
+ return -1;
+ memcpy(type, type_line + 5, typelen);
+ type[typelen] = '\0';
+ taglen = sig_line - tag_line - strlen("tag \n");
+ item->tag = xmalloc(taglen + 1);
+ memcpy(item->tag, tag_line + 4, taglen);
+ item->tag[taglen] = '\0';
+
+ item->tagged = lookup_object_type(object, type);
+ if (item->tagged && track_object_refs) {
+ struct object_refs *refs = alloc_object_refs(1);
+ refs->ref[0] = item->tagged;
+ set_object_refs(&item->object, refs);
+ }
+
+ return 0;
+}
+
+int parse_tag(struct tag *item)
+{
+ char type[20];
+ void *data;
+ unsigned long size;
+ int ret;
+
+ if (item->object.parsed)
+ return 0;
+ data = read_sha1_file(item->object.sha1, type, &size);
+ if (!data)
+ return error("Could not read %s",
+ sha1_to_hex(item->object.sha1));
+ if (strcmp(type, tag_type)) {
+ free(data);
+ return error("Object %s not a tag",
+ sha1_to_hex(item->object.sha1));
+ }
+ ret = parse_tag_buffer(item, data, size);
+ free(data);
+ return ret;
+}
diff --git a/tag.h b/tag.h
new file mode 100644
index 000000000..7a0cb0070
--- /dev/null
+++ b/tag.h
@@ -0,0 +1,20 @@
+#ifndef TAG_H
+#define TAG_H
+
+#include "object.h"
+
+extern const char *tag_type;
+
+struct tag {
+ struct object object;
+ struct object *tagged;
+ char *tag;
+ char *signature; /* not actually implemented */
+};
+
+extern struct tag *lookup_tag(const unsigned char *sha1);
+extern int parse_tag_buffer(struct tag *item, void *data, unsigned long size);
+extern int parse_tag(struct tag *item);
+extern struct object *deref_tag(struct object *, const char *, int);
+
+#endif /* TAG_H */
diff --git a/tar-tree.c b/tar-tree.c
new file mode 100644
index 000000000..970c4bb54
--- /dev/null
+++ b/tar-tree.c
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 2005 Rene Scharfe
+ */
+#include <time.h>
+#include "cache.h"
+
+#define RECORDSIZE (512)
+#define BLOCKSIZE (RECORDSIZE * 20)
+
+#define TYPEFLAG_AUTO '\0'
+#define TYPEFLAG_REG '0'
+#define TYPEFLAG_LNK '2'
+#define TYPEFLAG_DIR '5'
+#define TYPEFLAG_GLOBAL_HEADER 'g'
+#define TYPEFLAG_EXT_HEADER 'x'
+
+#define EXT_HEADER_PATH 1
+#define EXT_HEADER_LINKPATH 2
+
+static const char tar_tree_usage[] = "git-tar-tree <key> [basedir]";
+
+static char block[BLOCKSIZE];
+static unsigned long offset;
+
+static const char *basedir;
+static time_t archive_time;
+
+struct path_prefix {
+ struct path_prefix *prev;
+ const char *name;
+};
+
+/* tries hard to write, either succeeds or dies in the attempt */
+static void reliable_write(void *buf, unsigned long size)
+{
+ while (size > 0) {
+ long ret = write(1, buf, size);
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ continue;
+ if (errno == EPIPE)
+ exit(0);
+ die("git-tar-tree: %s", strerror(errno));
+ } else if (!ret) {
+ die("git-tar-tree: disk full?");
+ }
+ size -= ret;
+ buf += ret;
+ }
+}
+
+/* writes out the whole block, but only if it is full */
+static void write_if_needed(void)
+{
+ if (offset == BLOCKSIZE) {
+ reliable_write(block, BLOCKSIZE);
+ offset = 0;
+ }
+}
+
+/* acquire the next record from the buffer; user must call write_if_needed() */
+static char *get_record(void)
+{
+ char *p = block + offset;
+ memset(p, 0, RECORDSIZE);
+ offset += RECORDSIZE;
+ return p;
+}
+
+/*
+ * The end of tar archives is marked by 1024 nul bytes and after that
+ * follows the rest of the block (if any).
+ */
+static void write_trailer(void)
+{
+ get_record();
+ write_if_needed();
+ get_record();
+ write_if_needed();
+ while (offset) {
+ get_record();
+ write_if_needed();
+ }
+}
+
+/*
+ * queues up writes, so that all our write(2) calls write exactly one
+ * full block; pads writes to RECORDSIZE
+ */
+static void write_blocked(void *buf, unsigned long size)
+{
+ unsigned long tail;
+
+ if (offset) {
+ unsigned long chunk = BLOCKSIZE - offset;
+ if (size < chunk)
+ chunk = size;
+ memcpy(block + offset, buf, chunk);
+ size -= chunk;
+ offset += chunk;
+ buf += chunk;
+ write_if_needed();
+ }
+ while (size >= BLOCKSIZE) {
+ reliable_write(buf, BLOCKSIZE);
+ size -= BLOCKSIZE;
+ buf += BLOCKSIZE;
+ }
+ if (size) {
+ memcpy(block + offset, buf, size);
+ buf += size;
+ offset += size;
+ }
+ tail = offset % RECORDSIZE;
+ if (tail) {
+ memset(block + offset, 0, RECORDSIZE - tail);
+ offset += RECORDSIZE - tail;
+ }
+ write_if_needed();
+}
+
+static void append_string(char **p, const char *s)
+{
+ unsigned int len = strlen(s);
+ memcpy(*p, s, len);
+ *p += len;
+}
+
+static void append_char(char **p, char c)
+{
+ **p = c;
+ *p += 1;
+}
+
+static void append_path_prefix(char **buffer, struct path_prefix *prefix)
+{
+ if (!prefix)
+ return;
+ append_path_prefix(buffer, prefix->prev);
+ append_string(buffer, prefix->name);
+ append_char(buffer, '/');
+}
+
+static unsigned int path_prefix_len(struct path_prefix *prefix)
+{
+ if (!prefix)
+ return 0;
+ return path_prefix_len(prefix->prev) + strlen(prefix->name) + 1;
+}
+
+static void append_path(char **p, int is_dir, const char *basepath,
+ struct path_prefix *prefix, const char *path)
+{
+ if (basepath) {
+ append_string(p, basepath);
+ append_char(p, '/');
+ }
+ append_path_prefix(p, prefix);
+ append_string(p, path);
+ if (is_dir)
+ append_char(p, '/');
+}
+
+static unsigned int path_len(int is_dir, const char *basepath,
+ struct path_prefix *prefix, const char *path)
+{
+ unsigned int len = 0;
+ if (basepath)
+ len += strlen(basepath) + 1;
+ len += path_prefix_len(prefix) + strlen(path);
+ if (is_dir)
+ len++;
+ return len;
+}
+
+static void append_extended_header_prefix(char **p, unsigned int size,
+ const char *keyword)
+{
+ int len = sprintf(*p, "%u %s=", size, keyword);
+ *p += len;
+}
+
+static unsigned int extended_header_len(const char *keyword,
+ unsigned int valuelen)
+{
+ /* "%u %s=%s\n" */
+ unsigned int len = 1 + 1 + strlen(keyword) + 1 + valuelen + 1;
+ if (len > 9)
+ len++;
+ if (len > 99)
+ len++;
+ return len;
+}
+
+static void append_extended_header(char **p, const char *keyword,
+ const char *value, unsigned int len)
+{
+ unsigned int size = extended_header_len(keyword, len);
+ append_extended_header_prefix(p, size, keyword);
+ memcpy(*p, value, len);
+ *p += len;
+ append_char(p, '\n');
+}
+
+static void write_header(const unsigned char *, char, const char *, struct path_prefix *,
+ const char *, unsigned int, void *, unsigned long);
+
+/* stores a pax extended header directly in the block buffer */
+static void write_extended_header(const char *headerfilename, int is_dir,
+ unsigned int flags, const char *basepath,
+ struct path_prefix *prefix,
+ const char *path, unsigned int namelen,
+ void *content, unsigned int contentsize)
+{
+ char *buffer, *p;
+ unsigned int pathlen, size, linkpathlen = 0;
+
+ size = pathlen = extended_header_len("path", namelen);
+ if (flags & EXT_HEADER_LINKPATH) {
+ linkpathlen = extended_header_len("linkpath", contentsize);
+ size += linkpathlen;
+ }
+ write_header(NULL, TYPEFLAG_EXT_HEADER, NULL, NULL, headerfilename,
+ 0100600, NULL, size);
+
+ buffer = p = malloc(size);
+ if (!buffer)
+ die("git-tar-tree: %s", strerror(errno));
+ append_extended_header_prefix(&p, pathlen, "path");
+ append_path(&p, is_dir, basepath, prefix, path);
+ append_char(&p, '\n');
+ if (flags & EXT_HEADER_LINKPATH)
+ append_extended_header(&p, "linkpath", content, contentsize);
+ write_blocked(buffer, size);
+ free(buffer);
+}
+
+static void write_global_extended_header(const unsigned char *sha1)
+{
+ char *p;
+ unsigned int size;
+
+ size = extended_header_len("comment", 40);
+ write_header(NULL, TYPEFLAG_GLOBAL_HEADER, NULL, NULL,
+ "pax_global_header", 0100600, NULL, size);
+
+ p = get_record();
+ append_extended_header(&p, "comment", sha1_to_hex(sha1), 40);
+ write_if_needed();
+}
+
+/* stores a ustar header directly in the block buffer */
+static void write_header(const unsigned char *sha1, char typeflag, const char *basepath,
+ struct path_prefix *prefix, const char *path,
+ unsigned int mode, void *buffer, unsigned long size)
+{
+ unsigned int namelen;
+ char *header = NULL;
+ unsigned int checksum = 0;
+ int i;
+ unsigned int ext_header = 0;
+
+ if (typeflag == TYPEFLAG_AUTO) {
+ if (S_ISDIR(mode))
+ typeflag = TYPEFLAG_DIR;
+ else if (S_ISLNK(mode))
+ typeflag = TYPEFLAG_LNK;
+ else
+ typeflag = TYPEFLAG_REG;
+ }
+
+ namelen = path_len(S_ISDIR(mode), basepath, prefix, path);
+ if (namelen > 100)
+ ext_header |= EXT_HEADER_PATH;
+ if (typeflag == TYPEFLAG_LNK && size > 100)
+ ext_header |= EXT_HEADER_LINKPATH;
+
+ /* the extended header must be written before the normal one */
+ if (ext_header) {
+ char headerfilename[51];
+ sprintf(headerfilename, "%s.paxheader", sha1_to_hex(sha1));
+ write_extended_header(headerfilename, S_ISDIR(mode),
+ ext_header, basepath, prefix, path,
+ namelen, buffer, size);
+ }
+
+ header = get_record();
+
+ if (ext_header) {
+ sprintf(header, "%s.data", sha1_to_hex(sha1));
+ } else {
+ char *p = header;
+ append_path(&p, S_ISDIR(mode), basepath, prefix, path);
+ }
+
+ if (typeflag == TYPEFLAG_LNK) {
+ if (ext_header & EXT_HEADER_LINKPATH) {
+ sprintf(&header[157], "see %s.paxheader",
+ sha1_to_hex(sha1));
+ } else {
+ if (buffer)
+ strncpy(&header[157], buffer, size);
+ }
+ }
+
+ if (S_ISDIR(mode))
+ mode |= 0755; /* GIT doesn't store permissions of dirs */
+ if (S_ISLNK(mode))
+ mode |= 0777; /* ... nor of symlinks */
+ sprintf(&header[100], "%07o", mode & 07777);
+
+ /* XXX: should we provide more meaningful info here? */
+ sprintf(&header[108], "%07o", 0); /* uid */
+ sprintf(&header[116], "%07o", 0); /* gid */
+ strncpy(&header[265], "git", 31); /* uname */
+ strncpy(&header[297], "git", 31); /* gname */
+
+ if (S_ISDIR(mode) || S_ISLNK(mode))
+ size = 0;
+ sprintf(&header[124], "%011lo", size);
+ sprintf(&header[136], "%011lo", archive_time);
+
+ header[156] = typeflag;
+
+ memcpy(&header[257], "ustar", 6);
+ memcpy(&header[263], "00", 2);
+
+ sprintf(&header[329], "%07o", 0); /* devmajor */
+ sprintf(&header[337], "%07o", 0); /* devminor */
+
+ memset(&header[148], ' ', 8);
+ for (i = 0; i < RECORDSIZE; i++)
+ checksum += header[i];
+ sprintf(&header[148], "%07o", checksum & 0x1fffff);
+
+ write_if_needed();
+}
+
+static void traverse_tree(void *buffer, unsigned long size,
+ struct path_prefix *prefix)
+{
+ struct path_prefix this_prefix;
+ this_prefix.prev = prefix;
+
+ while (size) {
+ int namelen = strlen(buffer)+1;
+ void *eltbuf;
+ char elttype[20];
+ unsigned long eltsize;
+ unsigned char *sha1 = buffer + namelen;
+ char *path = strchr(buffer, ' ') + 1;
+ unsigned int mode;
+
+ if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
+ die("corrupt 'tree' file");
+ if (S_ISDIR(mode) || S_ISREG(mode))
+ mode |= (mode & 0100) ? 0777 : 0666;
+ buffer = sha1 + 20;
+ size -= namelen + 20;
+
+ eltbuf = read_sha1_file(sha1, elttype, &eltsize);
+ if (!eltbuf)
+ die("cannot read %s", sha1_to_hex(sha1));
+ write_header(sha1, TYPEFLAG_AUTO, basedir, prefix, path,
+ mode, eltbuf, eltsize);
+ if (!strcmp(elttype, "tree")) {
+ this_prefix.name = path;
+ traverse_tree(eltbuf, eltsize, &this_prefix);
+ } else if (!strcmp(elttype, "blob") && !S_ISLNK(mode)) {
+ write_blocked(eltbuf, eltsize);
+ }
+ free(eltbuf);
+ }
+}
+
+/* get commit time from committer line of commit object */
+static time_t commit_time(void * buffer, unsigned long size)
+{
+ time_t result = 0;
+ char *p = buffer;
+
+ while (size > 0) {
+ char *endp = memchr(p, '\n', size);
+ if (!endp || endp == p)
+ break;
+ *endp = '\0';
+ if (endp - p > 10 && !memcmp(p, "committer ", 10)) {
+ char *nump = strrchr(p, '>');
+ if (!nump)
+ break;
+ nump++;
+ result = strtoul(nump, &endp, 10);
+ if (*endp != ' ')
+ result = 0;
+ break;
+ }
+ size -= endp - p - 1;
+ p = endp + 1;
+ }
+ return result;
+}
+
+int main(int argc, char **argv)
+{
+ unsigned char sha1[20];
+ unsigned char commit_sha1[20];
+ void *buffer;
+ unsigned long size;
+
+ switch (argc) {
+ case 3:
+ basedir = argv[2];
+ /* FALLTHROUGH */
+ case 2:
+ if (get_sha1(argv[1], sha1) < 0)
+ usage(tar_tree_usage);
+ break;
+ default:
+ usage(tar_tree_usage);
+ }
+
+ buffer = read_object_with_reference(sha1, "commit", &size, commit_sha1);
+ if (buffer) {
+ write_global_extended_header(commit_sha1);
+ archive_time = commit_time(buffer, size);
+ free(buffer);
+ }
+ buffer = read_object_with_reference(sha1, "tree", &size, NULL);
+ if (!buffer)
+ die("not a reference to a tag, commit or tree object: %s",
+ sha1_to_hex(sha1));
+ if (!archive_time)
+ archive_time = time(NULL);
+ if (basedir)
+ write_header((unsigned char *)"0", TYPEFLAG_DIR, NULL, NULL,
+ basedir, 040755, NULL, 0);
+ traverse_tree(buffer, size, NULL);
+ free(buffer);
+ write_trailer();
+ return 0;
+}
diff --git a/templates/.gitignore b/templates/.gitignore
new file mode 100644
index 000000000..6759ecbf9
--- /dev/null
+++ b/templates/.gitignore
@@ -0,0 +1,2 @@
+blt
+boilerplates.made
diff --git a/templates/Makefile b/templates/Makefile
new file mode 100644
index 000000000..8f7f4fec3
--- /dev/null
+++ b/templates/Makefile
@@ -0,0 +1,48 @@
+# make and install sample templates
+
+INSTALL ?= install
+TAR ?= tar
+prefix ?= $(HOME)
+template_dir ?= $(prefix)/share/git-core/templates/
+# DESTDIR=
+
+# Shell quote;
+# Result of this needs to be placed inside ''
+shq = $(subst ','\'',$(1))
+# This has surrounding ''
+shellquote = '$(call shq,$(1))'
+
+all: boilerplates.made custom
+
+# Put templates that can be copied straight from the source
+# in a file direc--tory--file in the source. They will be
+# just copied to the destination.
+
+bpsrc = $(filter-out %~,$(wildcard *--*))
+boilerplates.made : $(bpsrc)
+ ls *--* 2>/dev/null | \
+ while read boilerplate; \
+ do \
+ case "$$boilerplate" in *~) continue ;; esac && \
+ dst=`echo "$$boilerplate" | sed -e 's|^this|.|;s|--|/|g'` && \
+ dir=`expr "$$dst" : '\(.*\)/'` && \
+ mkdir -p blt/$$dir && \
+ case "$$boilerplate" in \
+ *--) ;; \
+ *) cp $$boilerplate blt/$$dst ;; \
+ esac || exit; \
+ done || exit
+ date >$@
+
+# If you need build-tailored templates, build them into blt/
+# directory yourself here.
+custom:
+ : no custom templates yet
+
+clean:
+ rm -rf blt boilerplates.made
+
+install: all
+ $(INSTALL) -d -m755 $(call shellquote,$(DESTDIR)$(template_dir))
+ (cd blt && $(TAR) cf - .) | \
+ (cd $(call shellquote,$(DESTDIR)$(template_dir)) && $(TAR) xf -)
diff --git a/templates/branches-- b/templates/branches--
new file mode 100644
index 000000000..fae88709a
--- /dev/null
+++ b/templates/branches--
@@ -0,0 +1 @@
+: this is just to ensure the directory exists.
diff --git a/templates/hooks--applypatch-msg b/templates/hooks--applypatch-msg
new file mode 100644
index 000000000..bda3c86be
--- /dev/null
+++ b/templates/hooks--applypatch-msg
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit. The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, make this file executable.
+
+test -x "$GIT_DIR/hooks/commit-msg" &&
+ exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:
diff --git a/templates/hooks--commit-msg b/templates/hooks--commit-msg
new file mode 100644
index 000000000..643822d23
--- /dev/null
+++ b/templates/hooks--commit-msg
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by git-commit with one argument, the name of the file
+# that has the commit message. The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit. The hook is allowed to edit the commit message file.
+#
+# To enable this hook, make this file executable.
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+ sort | uniq -c | sed -e '/^[ ]*1 /d')"
diff --git a/templates/hooks--post-commit b/templates/hooks--post-commit
new file mode 100644
index 000000000..8be6f34ad
--- /dev/null
+++ b/templates/hooks--post-commit
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, make this file executable.
+
+: Nothing
diff --git a/templates/hooks--post-update b/templates/hooks--post-update
new file mode 100644
index 000000000..bcba8937b
--- /dev/null
+++ b/templates/hooks--post-update
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, make this file executable by "chmod +x post-update".
+
+exec git-update-server-info
diff --git a/templates/hooks--pre-applypatch b/templates/hooks--pre-applypatch
new file mode 100644
index 000000000..a54751600
--- /dev/null
+++ b/templates/hooks--pre-applypatch
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, make this file executable.
+
+test -x "$GIT_DIR/hooks/pre-commit" &&
+ exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:
+
diff --git a/templates/hooks--pre-commit b/templates/hooks--pre-commit
new file mode 100644
index 000000000..4bb6803b1
--- /dev/null
+++ b/templates/hooks--pre-commit
@@ -0,0 +1,61 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by git-commit with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, make this file executable.
+
+# This is slightly modified from Andrew Morton's Perfect Patch.
+# Lines you introduce should not have trailing whitespace.
+# Also check for an indentation that has SP before a TAB.
+perl -e '
+ my $fh;
+ my $found_bad = 0;
+ my $filename;
+ my $reported_filename = "";
+ my $lineno;
+ sub bad_line {
+ my ($why, $line) = @_;
+ if (!$found_bad) {
+ print STDERR "*\n";
+ print STDERR "* You have some suspicious patch lines:\n";
+ print STDERR "*\n";
+ $found_bad = 1;
+ }
+ if ($reported_filename ne $filename) {
+ print STDERR "* In $filename\n";
+ $reported_filename = $filename;
+ }
+ print STDERR "* $why (line $lineno)\n";
+ print STDERR "$filename:$lineno:$line\n";
+ }
+ open $fh, "-|", qw(git-diff-index -p -M --cached HEAD);
+ while (<$fh>) {
+ if (m|^diff --git a/(.*) b/\1$|) {
+ $filename = $1;
+ next;
+ }
+ if (/^@@ -\S+ \+(\d+)/) {
+ $lineno = $1 - 1;
+ next;
+ }
+ if (/^ /) {
+ $lineno++;
+ next;
+ }
+ if (s/^\+//) {
+ $lineno++;
+ chomp;
+ if (/\s$/) {
+ bad_line("trailing whitespace", $_);
+ }
+ if (/^\s* /) {
+ bad_line("indent SP followed by a TAB", $_);
+ }
+ }
+ }
+ exit($found_bad);
+'
+
diff --git a/templates/hooks--update b/templates/hooks--update
new file mode 100644
index 000000000..3f38b82a4
--- /dev/null
+++ b/templates/hooks--update
@@ -0,0 +1,31 @@
+#!/bin/sh
+#
+# An example hook script to mail out commit update information.
+# Called by git-receive-pack with arguments: refname sha1-old sha1-new
+#
+# To enable this hook:
+# (1) change the recipient e-mail address
+# (2) make this file executable by "chmod +x update".
+#
+
+recipient="commit-list@mydomain.xz"
+
+if expr "$2" : '0*$' >/dev/null
+then
+ echo "Created a new ref, with the following commits:"
+ git-rev-list --pretty "$3"
+else
+ $base=$(git-merge-base "$2" "$3")
+ case "$base" in
+ "$2")
+ echo "New commits:"
+ ;;
+ *)
+ echo "Rebased ref, commits from common ancestor:"
+ ;;
+ esac
+fi
+git-rev-list --pretty "$3" "^$base"
+fi |
+mail -s "Changes to ref $1" "$recipient"
+exit 0
diff --git a/templates/info--exclude b/templates/info--exclude
new file mode 100644
index 000000000..2c87b72df
--- /dev/null
+++ b/templates/info--exclude
@@ -0,0 +1,6 @@
+# git-ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/templates/remotes-- b/templates/remotes--
new file mode 100644
index 000000000..fae88709a
--- /dev/null
+++ b/templates/remotes--
@@ -0,0 +1 @@
+: this is just to ensure the directory exists.
diff --git a/templates/this--description b/templates/this--description
new file mode 100644
index 000000000..c6f25e80b
--- /dev/null
+++ b/templates/this--description
@@ -0,0 +1 @@
+Unnamed repository; edit this file to name it for gitweb.
diff --git a/test-date.c b/test-date.c
new file mode 100644
index 000000000..93e802759
--- /dev/null
+++ b/test-date.c
@@ -0,0 +1,23 @@
+#include <stdio.h>
+#include <time.h>
+
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ char result[100];
+ time_t t;
+
+ memcpy(result, "bad", 4);
+ parse_date(argv[i], result, sizeof(result));
+ t = strtoul(result, NULL, 0);
+ printf("%s -> %s -> %s", argv[i], result, ctime(&t));
+
+ t = approxidate(argv[i]);
+ printf("%s -> %s\n", argv[i], ctime(&t));
+ }
+ return 0;
+}
diff --git a/test-delta.c b/test-delta.c
new file mode 100644
index 000000000..1be8ee0c7
--- /dev/null
+++ b/test-delta.c
@@ -0,0 +1,83 @@
+/*
+ * test-delta.c: test code to exercise diff-delta.c and patch-delta.c
+ *
+ * (C) 2005 Nicolas Pitre <nico@cam.org>
+ *
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include "delta.h"
+
+static const char usage[] =
+ "test-delta (-d|-p) <from_file> <data_file> <out_file>";
+
+int main(int argc, char *argv[])
+{
+ int fd;
+ struct stat st;
+ void *from_buf, *data_buf, *out_buf;
+ unsigned long from_size, data_size, out_size;
+
+ if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) {
+ fprintf(stderr, "Usage: %s\n", usage);
+ return 1;
+ }
+
+ fd = open(argv[2], O_RDONLY);
+ if (fd < 0 || fstat(fd, &st)) {
+ perror(argv[2]);
+ return 1;
+ }
+ from_size = st.st_size;
+ from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (from_buf == MAP_FAILED) {
+ perror(argv[2]);
+ close(fd);
+ return 1;
+ }
+ close(fd);
+
+ fd = open(argv[3], O_RDONLY);
+ if (fd < 0 || fstat(fd, &st)) {
+ perror(argv[3]);
+ return 1;
+ }
+ data_size = st.st_size;
+ data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (data_buf == MAP_FAILED) {
+ perror(argv[3]);
+ close(fd);
+ return 1;
+ }
+ close(fd);
+
+ if (argv[1][1] == 'd')
+ out_buf = diff_delta(from_buf, from_size,
+ data_buf, data_size,
+ &out_size, 0);
+ else
+ out_buf = patch_delta(from_buf, from_size,
+ data_buf, data_size,
+ &out_size);
+ if (!out_buf) {
+ fprintf(stderr, "delta operation failed (returned NULL)\n");
+ return 1;
+ }
+
+ fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if (fd < 0 || write(fd, out_buf, out_size) != out_size) {
+ perror(argv[4]);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/tree-diff.c b/tree-diff.c
new file mode 100644
index 000000000..0ef06a9e3
--- /dev/null
+++ b/tree-diff.c
@@ -0,0 +1,270 @@
+/*
+ * Helper functions for tree diff generation
+ */
+#include "cache.h"
+#include "diff.h"
+
+// What paths are we interested in?
+static int nr_paths = 0;
+static const char **paths = NULL;
+static int *pathlens = NULL;
+
+static void update_tree_entry(struct tree_desc *desc)
+{
+ void *buf = desc->buf;
+ unsigned long size = desc->size;
+ int len = strlen(buf) + 1 + 20;
+
+ if (size < len)
+ die("corrupt tree file");
+ desc->buf = buf + len;
+ desc->size = size - len;
+}
+
+static const unsigned char *extract(struct tree_desc *desc, const char **pathp, unsigned int *modep)
+{
+ void *tree = desc->buf;
+ unsigned long size = desc->size;
+ int len = strlen(tree)+1;
+ const unsigned char *sha1 = tree + len;
+ const char *path = strchr(tree, ' ');
+ unsigned int mode;
+
+ if (!path || size < len + 20 || sscanf(tree, "%o", &mode) != 1)
+ die("corrupt tree file");
+ *pathp = path+1;
+ *modep = DIFF_FILE_CANON_MODE(mode);
+ return sha1;
+}
+
+static char *malloc_base(const char *base, const char *path, int pathlen)
+{
+ int baselen = strlen(base);
+ char *newbase = xmalloc(baselen + pathlen + 2);
+ memcpy(newbase, base, baselen);
+ memcpy(newbase + baselen, path, pathlen);
+ memcpy(newbase + baselen + pathlen, "/", 2);
+ return newbase;
+}
+
+static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base);
+
+static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+ unsigned mode1, mode2;
+ const char *path1, *path2;
+ const unsigned char *sha1, *sha2;
+ int cmp, pathlen1, pathlen2;
+
+ sha1 = extract(t1, &path1, &mode1);
+ sha2 = extract(t2, &path2, &mode2);
+
+ pathlen1 = strlen(path1);
+ pathlen2 = strlen(path2);
+ cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
+ if (cmp < 0) {
+ show_entry(opt, "-", t1, base);
+ return -1;
+ }
+ if (cmp > 0) {
+ show_entry(opt, "+", t2, base);
+ return 1;
+ }
+ if (!opt->find_copies_harder &&
+ !memcmp(sha1, sha2, 20) && mode1 == mode2)
+ return 0;
+
+ /*
+ * If the filemode has changed to/from a directory from/to a regular
+ * file, we need to consider it a remove and an add.
+ */
+ if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
+ show_entry(opt, "-", t1, base);
+ show_entry(opt, "+", t2, base);
+ return 0;
+ }
+
+ if (opt->recursive && S_ISDIR(mode1)) {
+ int retval;
+ char *newbase = malloc_base(base, path1, pathlen1);
+ if (opt->tree_in_recursive)
+ opt->change(opt, mode1, mode2,
+ sha1, sha2, base, path1);
+ retval = diff_tree_sha1(sha1, sha2, newbase, opt);
+ free(newbase);
+ return retval;
+ }
+
+ opt->change(opt, mode1, mode2, sha1, sha2, base, path1);
+ return 0;
+}
+
+static int interesting(struct tree_desc *desc, const char *base)
+{
+ const char *path;
+ unsigned mode;
+ int i;
+ int baselen, pathlen;
+
+ if (!nr_paths)
+ return 1;
+
+ (void)extract(desc, &path, &mode);
+
+ pathlen = strlen(path);
+ baselen = strlen(base);
+
+ for (i=0; i < nr_paths; i++) {
+ const char *match = paths[i];
+ int matchlen = pathlens[i];
+
+ if (baselen >= matchlen) {
+ /* If it doesn't match, move along... */
+ if (strncmp(base, match, matchlen))
+ continue;
+
+ /* The base is a subdirectory of a path which was specified. */
+ return 1;
+ }
+
+ /* Does the base match? */
+ if (strncmp(base, match, baselen))
+ continue;
+
+ match += baselen;
+ matchlen -= baselen;
+
+ if (pathlen > matchlen)
+ continue;
+
+ if (matchlen > pathlen) {
+ if (match[pathlen] != '/')
+ continue;
+ if (!S_ISDIR(mode))
+ continue;
+ }
+
+ if (strncmp(path, match, pathlen))
+ continue;
+
+ return 1;
+ }
+ return 0; /* No matches */
+}
+
+/* A whole sub-tree went away or appeared */
+static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+{
+ while (desc->size) {
+ if (interesting(desc, base))
+ show_entry(opt, prefix, desc, base);
+ update_tree_entry(desc);
+ }
+}
+
+/* A file entry went away or appeared */
+static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base)
+{
+ unsigned mode;
+ const char *path;
+ const unsigned char *sha1 = extract(desc, &path, &mode);
+
+ if (opt->recursive && S_ISDIR(mode)) {
+ char type[20];
+ char *newbase = malloc_base(base, path, strlen(path));
+ struct tree_desc inner;
+ void *tree;
+
+ tree = read_sha1_file(sha1, type, &inner.size);
+ if (!tree || strcmp(type, "tree"))
+ die("corrupt tree sha %s", sha1_to_hex(sha1));
+
+ inner.buf = tree;
+ show_tree(opt, prefix, &inner, newbase);
+
+ free(tree);
+ free(newbase);
+ return 0;
+ }
+
+ opt->add_remove(opt, prefix[0], mode, sha1, base, path);
+ return 0;
+}
+
+int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+ while (t1->size | t2->size) {
+ if (nr_paths && t1->size && !interesting(t1, base)) {
+ update_tree_entry(t1);
+ continue;
+ }
+ if (nr_paths && t2->size && !interesting(t2, base)) {
+ update_tree_entry(t2);
+ continue;
+ }
+ if (!t1->size) {
+ show_entry(opt, "+", t2, base);
+ update_tree_entry(t2);
+ continue;
+ }
+ if (!t2->size) {
+ show_entry(opt, "-", t1, base);
+ update_tree_entry(t1);
+ continue;
+ }
+ switch (compare_tree_entry(t1, t2, base, opt)) {
+ case -1:
+ update_tree_entry(t1);
+ continue;
+ case 0:
+ update_tree_entry(t1);
+ /* Fallthrough */
+ case 1:
+ update_tree_entry(t2);
+ continue;
+ }
+ die("git-diff-tree: internal error");
+ }
+ return 0;
+}
+
+int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
+{
+ void *tree1, *tree2;
+ struct tree_desc t1, t2;
+ int retval;
+
+ tree1 = read_object_with_reference(old, "tree", &t1.size, NULL);
+ if (!tree1)
+ die("unable to read source tree (%s)", sha1_to_hex(old));
+ tree2 = read_object_with_reference(new, "tree", &t2.size, NULL);
+ if (!tree2)
+ die("unable to read destination tree (%s)", sha1_to_hex(new));
+ t1.buf = tree1;
+ t2.buf = tree2;
+ retval = diff_tree(&t1, &t2, base, opt);
+ free(tree1);
+ free(tree2);
+ return retval;
+}
+
+static int count_paths(const char **paths)
+{
+ int i = 0;
+ while (*paths++)
+ i++;
+ return i;
+}
+
+void diff_tree_setup_paths(const char **p)
+{
+ if (p) {
+ int i;
+
+ paths = p;
+ nr_paths = count_paths(paths);
+ pathlens = xmalloc(nr_paths * sizeof(int));
+ for (i=0; i<nr_paths; i++)
+ pathlens[i] = strlen(paths[i]);
+ }
+}
diff --git a/tree.c b/tree.c
new file mode 100644
index 000000000..8b42a07b2
--- /dev/null
+++ b/tree.c
@@ -0,0 +1,246 @@
+#include "tree.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "cache.h"
+#include <stdlib.h>
+
+const char *tree_type = "tree";
+
+static int read_one_entry(unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+{
+ int len = strlen(pathname);
+ unsigned int size = cache_entry_size(baselen + len);
+ struct cache_entry *ce = xmalloc(size);
+
+ memset(ce, 0, size);
+
+ ce->ce_mode = create_ce_mode(mode);
+ ce->ce_flags = create_ce_flags(baselen + len, stage);
+ memcpy(ce->name, base, baselen);
+ memcpy(ce->name + baselen, pathname, len+1);
+ memcpy(ce->sha1, sha1, 20);
+ return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+}
+
+static int match_tree_entry(const char *base, int baselen, const char *path, unsigned int mode, const char **paths)
+{
+ const char *match;
+ int pathlen;
+
+ if (!paths)
+ return 1;
+ pathlen = strlen(path);
+ while ((match = *paths++) != NULL) {
+ int matchlen = strlen(match);
+
+ if (baselen >= matchlen) {
+ /* If it doesn't match, move along... */
+ if (strncmp(base, match, matchlen))
+ continue;
+ /* The base is a subdirectory of a path which was specified. */
+ return 1;
+ }
+
+ /* Does the base match? */
+ if (strncmp(base, match, baselen))
+ continue;
+
+ match += baselen;
+ matchlen -= baselen;
+
+ if (pathlen > matchlen)
+ continue;
+
+ if (matchlen > pathlen) {
+ if (match[pathlen] != '/')
+ continue;
+ if (!S_ISDIR(mode))
+ continue;
+ }
+
+ if (strncmp(path, match, pathlen))
+ continue;
+
+ return 1;
+ }
+ return 0;
+}
+
+static int read_tree_recursive(void *buffer, unsigned long size,
+ const char *base, int baselen,
+ int stage, const char **match)
+{
+ while (size) {
+ int len = strlen(buffer)+1;
+ unsigned char *sha1 = buffer + len;
+ char *path = strchr(buffer, ' ')+1;
+ unsigned int mode;
+
+ if (size < len + 20 || sscanf(buffer, "%o", &mode) != 1)
+ return -1;
+
+ buffer = sha1 + 20;
+ size -= len + 20;
+
+ if (!match_tree_entry(base, baselen, path, mode, match))
+ continue;
+
+ if (S_ISDIR(mode)) {
+ int retval;
+ int pathlen = strlen(path);
+ char *newbase;
+ void *eltbuf;
+ char elttype[20];
+ unsigned long eltsize;
+
+ eltbuf = read_sha1_file(sha1, elttype, &eltsize);
+ if (!eltbuf || strcmp(elttype, "tree")) {
+ if (eltbuf) free(eltbuf);
+ return -1;
+ }
+ newbase = xmalloc(baselen + 1 + pathlen);
+ memcpy(newbase, base, baselen);
+ memcpy(newbase + baselen, path, pathlen);
+ newbase[baselen + pathlen] = '/';
+ retval = read_tree_recursive(eltbuf, eltsize,
+ newbase,
+ baselen + pathlen + 1,
+ stage, match);
+ free(eltbuf);
+ free(newbase);
+ if (retval)
+ return -1;
+ continue;
+ }
+ if (read_one_entry(sha1, base, baselen, path, mode, stage) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+int read_tree(void *buffer, unsigned long size, int stage, const char **match)
+{
+ return read_tree_recursive(buffer, size, "", 0, stage, match);
+}
+
+struct tree *lookup_tree(const unsigned char *sha1)
+{
+ struct object *obj = lookup_object(sha1);
+ if (!obj) {
+ struct tree *ret = xmalloc(sizeof(struct tree));
+ memset(ret, 0, sizeof(struct tree));
+ created_object(sha1, &ret->object);
+ ret->object.type = tree_type;
+ return ret;
+ }
+ if (!obj->type)
+ obj->type = tree_type;
+ if (obj->type != tree_type) {
+ error("Object %s is a %s, not a tree",
+ sha1_to_hex(sha1), obj->type);
+ return NULL;
+ }
+ return (struct tree *) obj;
+}
+
+int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size)
+{
+ void *bufptr = buffer;
+ struct tree_entry_list **list_p;
+ int n_refs = 0;
+
+ if (item->object.parsed)
+ return 0;
+ item->object.parsed = 1;
+ list_p = &item->entries;
+ while (size) {
+ struct object *obj;
+ struct tree_entry_list *entry;
+ int len = 1+strlen(bufptr);
+ unsigned char *file_sha1 = bufptr + len;
+ char *path = strchr(bufptr, ' ');
+ unsigned int mode;
+ if (size < len + 20 || !path ||
+ sscanf(bufptr, "%o", &mode) != 1)
+ return -1;
+
+ entry = xmalloc(sizeof(struct tree_entry_list));
+ entry->name = strdup(path + 1);
+ entry->directory = S_ISDIR(mode) != 0;
+ entry->executable = (mode & S_IXUSR) != 0;
+ entry->symlink = S_ISLNK(mode) != 0;
+ entry->zeropad = *(char *)bufptr == '0';
+ entry->mode = mode;
+ entry->next = NULL;
+
+ bufptr += len + 20;
+ size -= len + 20;
+
+ if (entry->directory) {
+ entry->item.tree = lookup_tree(file_sha1);
+ obj = &entry->item.tree->object;
+ } else {
+ entry->item.blob = lookup_blob(file_sha1);
+ obj = &entry->item.blob->object;
+ }
+ if (obj)
+ n_refs++;
+ entry->parent = NULL; /* needs to be filled by the user */
+ *list_p = entry;
+ list_p = &entry->next;
+ }
+
+ if (track_object_refs) {
+ struct tree_entry_list *entry;
+ unsigned i = 0;
+ struct object_refs *refs = alloc_object_refs(n_refs);
+ for (entry = item->entries; entry; entry = entry->next)
+ refs->ref[i++] = entry->item.any;
+ set_object_refs(&item->object, refs);
+ }
+
+ return 0;
+}
+
+int parse_tree(struct tree *item)
+{
+ char type[20];
+ void *buffer;
+ unsigned long size;
+ int ret;
+
+ if (item->object.parsed)
+ return 0;
+ buffer = read_sha1_file(item->object.sha1, type, &size);
+ if (!buffer)
+ return error("Could not read %s",
+ sha1_to_hex(item->object.sha1));
+ if (strcmp(type, tree_type)) {
+ free(buffer);
+ return error("Object %s not a tree",
+ sha1_to_hex(item->object.sha1));
+ }
+ ret = parse_tree_buffer(item, buffer, size);
+ free(buffer);
+ return ret;
+}
+
+struct tree *parse_tree_indirect(const unsigned char *sha1)
+{
+ struct object *obj = parse_object(sha1);
+ do {
+ if (!obj)
+ return NULL;
+ if (obj->type == tree_type)
+ return (struct tree *) obj;
+ else if (obj->type == commit_type)
+ obj = &(((struct commit *) obj)->tree->object);
+ else if (obj->type == tag_type)
+ obj = ((struct tag *) obj)->tagged;
+ else
+ return NULL;
+ if (!obj->parsed)
+ parse_object(obj->sha1);
+ } while (1);
+}
diff --git a/tree.h b/tree.h
new file mode 100644
index 000000000..9975e8821
--- /dev/null
+++ b/tree.h
@@ -0,0 +1,38 @@
+#ifndef TREE_H
+#define TREE_H
+
+#include "object.h"
+
+extern const char *tree_type;
+
+struct tree_entry_list {
+ struct tree_entry_list *next;
+ unsigned directory : 1;
+ unsigned executable : 1;
+ unsigned symlink : 1;
+ unsigned zeropad : 1;
+ unsigned int mode;
+ char *name;
+ union {
+ struct object *any;
+ struct tree *tree;
+ struct blob *blob;
+ } item;
+ struct tree_entry_list *parent;
+};
+
+struct tree {
+ struct object object;
+ struct tree_entry_list *entries;
+};
+
+struct tree *lookup_tree(const unsigned char *sha1);
+
+int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size);
+
+int parse_tree(struct tree *tree);
+
+/* Parses and returns the tree in the given ent, chasing tags and commits. */
+struct tree *parse_tree_indirect(const unsigned char *sha1);
+
+#endif /* TREE_H */
diff --git a/unpack-file.c b/unpack-file.c
new file mode 100644
index 000000000..d4ac3a546
--- /dev/null
+++ b/unpack-file.c
@@ -0,0 +1,34 @@
+#include "cache.h"
+
+static char *create_temp_file(unsigned char *sha1)
+{
+ static char path[50];
+ void *buf;
+ char type[100];
+ unsigned long size;
+ int fd;
+
+ buf = read_sha1_file(sha1, type, &size);
+ if (!buf || strcmp(type, "blob"))
+ die("unable to read blob object %s", sha1_to_hex(sha1));
+
+ strcpy(path, ".merge_file_XXXXXX");
+ fd = mkstemp(path);
+ if (fd < 0)
+ die("unable to create temp-file");
+ if (write(fd, buf, size) != size)
+ die("unable to write temp-file");
+ close(fd);
+ return path;
+}
+
+int main(int argc, char **argv)
+{
+ unsigned char sha1[20];
+
+ if (argc != 2 || get_sha1(argv[1], sha1))
+ usage("git-unpack-file <sha1>");
+
+ puts(create_temp_file(sha1));
+ return 0;
+}
diff --git a/unpack-objects.c b/unpack-objects.c
new file mode 100644
index 000000000..8490895cf
--- /dev/null
+++ b/unpack-objects.c
@@ -0,0 +1,316 @@
+#include "cache.h"
+#include "object.h"
+#include "delta.h"
+#include "pack.h"
+
+#include <sys/time.h>
+
+static int dry_run, quiet;
+static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file";
+
+/* We always read in 4kB chunks. */
+static unsigned char buffer[4096];
+static unsigned long offset, len, eof;
+static SHA_CTX ctx;
+
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void * fill(int min)
+{
+ if (min <= len)
+ return buffer + offset;
+ if (eof)
+ die("unable to fill input");
+ if (min > sizeof(buffer))
+ die("cannot fill %d bytes", min);
+ if (offset) {
+ SHA1_Update(&ctx, buffer, offset);
+ memcpy(buffer, buffer + offset, len);
+ offset = 0;
+ }
+ do {
+ int ret = read(0, buffer + len, sizeof(buffer) - len);
+ if (ret <= 0) {
+ if (!ret)
+ die("early EOF");
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ die("read error on input: %s", strerror(errno));
+ }
+ len += ret;
+ } while (len < min);
+ return buffer;
+}
+
+static void use(int bytes)
+{
+ if (bytes > len)
+ die("used more bytes than were available");
+ len -= bytes;
+ offset += bytes;
+}
+
+static void *get_data(unsigned long size)
+{
+ z_stream stream;
+ void *buf = xmalloc(size);
+
+ memset(&stream, 0, sizeof(stream));
+
+ stream.next_out = buf;
+ stream.avail_out = size;
+ stream.next_in = fill(1);
+ stream.avail_in = len;
+ inflateInit(&stream);
+
+ for (;;) {
+ int ret = inflate(&stream, 0);
+ use(len - stream.avail_in);
+ if (stream.total_out == size && ret == Z_STREAM_END)
+ break;
+ if (ret != Z_OK)
+ die("inflate returned %d\n", ret);
+ stream.next_in = fill(1);
+ stream.avail_in = len;
+ }
+ inflateEnd(&stream);
+ return buf;
+}
+
+struct delta_info {
+ unsigned char base_sha1[20];
+ unsigned long size;
+ void *delta;
+ struct delta_info *next;
+};
+
+static struct delta_info *delta_list;
+
+static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
+{
+ struct delta_info *info = xmalloc(sizeof(*info));
+
+ memcpy(info->base_sha1, base_sha1, 20);
+ info->size = size;
+ info->delta = delta;
+ info->next = delta_list;
+ delta_list = info;
+}
+
+static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+
+static void write_object(void *buf, unsigned long size, const char *type)
+{
+ unsigned char sha1[20];
+ if (write_sha1_file(buf, size, type, sha1) < 0)
+ die("failed to write object");
+ added_object(sha1, type, buf, size);
+}
+
+static int resolve_delta(const char *type,
+ void *base, unsigned long base_size,
+ void *delta, unsigned long delta_size)
+{
+ void *result;
+ unsigned long result_size;
+
+ result = patch_delta(base, base_size,
+ delta, delta_size,
+ &result_size);
+ if (!result)
+ die("failed to apply delta");
+ free(delta);
+ write_object(result, result_size, type);
+ free(result);
+ return 0;
+}
+
+static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+{
+ struct delta_info **p = &delta_list;
+ struct delta_info *info;
+
+ while ((info = *p) != NULL) {
+ if (!memcmp(info->base_sha1, sha1, 20)) {
+ *p = info->next;
+ p = &delta_list;
+ resolve_delta(type, data, size, info->delta, info->size);
+ free(info);
+ continue;
+ }
+ p = &info->next;
+ }
+}
+
+static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
+{
+ void *buf = get_data(size);
+ const char *type;
+
+ switch (kind) {
+ case OBJ_COMMIT: type = "commit"; break;
+ case OBJ_TREE: type = "tree"; break;
+ case OBJ_BLOB: type = "blob"; break;
+ case OBJ_TAG: type = "tag"; break;
+ default: die("bad type %d", kind);
+ }
+ if (!dry_run)
+ write_object(buf, size, type);
+ free(buf);
+ return 0;
+}
+
+static int unpack_delta_entry(unsigned long delta_size)
+{
+ void *delta_data, *base;
+ unsigned long base_size;
+ char type[20];
+ unsigned char base_sha1[20];
+ int result;
+
+ memcpy(base_sha1, fill(20), 20);
+ use(20);
+
+ delta_data = get_data(delta_size);
+ if (dry_run) {
+ free(delta_data);
+ return 0;
+ }
+
+ if (!has_sha1_file(base_sha1)) {
+ add_delta_to_list(base_sha1, delta_data, delta_size);
+ return 0;
+ }
+ base = read_sha1_file(base_sha1, type, &base_size);
+ if (!base)
+ die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1));
+ result = resolve_delta(type, base, base_size, delta_data, delta_size);
+ free(base);
+ return result;
+}
+
+static void unpack_one(unsigned nr, unsigned total)
+{
+ unsigned shift;
+ unsigned char *pack, c;
+ unsigned long size;
+ enum object_type type;
+
+ pack = fill(1);
+ c = *pack;
+ use(1);
+ type = (c >> 4) & 7;
+ size = (c & 15);
+ shift = 4;
+ while (c & 0x80) {
+ pack = fill(1);
+ c = *pack++;
+ use(1);
+ size += (c & 0x7f) << shift;
+ shift += 7;
+ }
+ if (!quiet) {
+ static unsigned long last_sec;
+ static unsigned last_percent;
+ struct timeval now;
+ unsigned percentage = (nr * 100) / total;
+
+ gettimeofday(&now, NULL);
+ if (percentage != last_percent || now.tv_sec != last_sec) {
+ last_sec = now.tv_sec;
+ last_percent = percentage;
+ fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total);
+ }
+ }
+ switch (type) {
+ case OBJ_COMMIT:
+ case OBJ_TREE:
+ case OBJ_BLOB:
+ case OBJ_TAG:
+ unpack_non_delta_entry(type, size);
+ return;
+ case OBJ_DELTA:
+ unpack_delta_entry(size);
+ return;
+ default:
+ die("bad object type %d", type);
+ }
+}
+
+/*
+ * We unpack from the end, older files first. Now, usually
+ * there are deltas etc, so we'll not actually write the
+ * objects in that order, but we might as well try..
+ */
+static void unpack_all(void)
+{
+ int i;
+ struct pack_header *hdr = fill(sizeof(struct pack_header));
+ unsigned version = ntohl(hdr->hdr_version);
+ unsigned nr_objects = ntohl(hdr->hdr_entries);
+
+ if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE)
+ die("bad pack file");
+ if (version != PACK_VERSION)
+ die("unable to handle pack file version %d", version);
+ fprintf(stderr, "Unpacking %d objects\n", nr_objects);
+
+ use(sizeof(struct pack_header));
+ for (i = 0; i < nr_objects; i++)
+ unpack_one(i+1, nr_objects);
+ if (delta_list)
+ die("unresolved deltas left after unpacking");
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ unsigned char sha1[20];
+
+ for (i = 1 ; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (*arg == '-') {
+ if (!strcmp(arg, "-n")) {
+ dry_run = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-q")) {
+ quiet = 1;
+ continue;
+ }
+ usage(unpack_usage);
+ }
+
+ /* We don't take any non-flag arguments now.. Maybe some day */
+ usage(unpack_usage);
+ }
+ SHA1_Init(&ctx);
+ unpack_all();
+ SHA1_Update(&ctx, buffer, offset);
+ SHA1_Final(sha1, &ctx);
+ if (memcmp(fill(20), sha1, 20))
+ die("final sha1 did not match");
+ use(20);
+
+ /* Write the last part of the buffer to stdout */
+ while (len) {
+ int ret = write(1, buffer + offset, len);
+ if (!ret)
+ break;
+ if (ret < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ break;
+ }
+ len -= ret;
+ offset += ret;
+ }
+
+ /* All done */
+ if (!quiet)
+ fprintf(stderr, "\n");
+ return 0;
+}
diff --git a/update-index.c b/update-index.c
new file mode 100644
index 000000000..5bbc3de28
--- /dev/null
+++ b/update-index.c
@@ -0,0 +1,518 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
+
+/*
+ * Default to not allowing changes to the list of files. The
+ * tool doesn't actually care, but this makes it harder to add
+ * files to the revision control by mistake by doing something
+ * like "git-update-index *" and suddenly having all the object
+ * files be revision controlled.
+ */
+static int allow_add;
+static int allow_remove;
+static int allow_replace;
+static int allow_unmerged; /* --refresh needing merge is not error */
+static int not_new; /* --refresh not having working tree files is not error */
+static int quiet; /* --refresh needing update is not error */
+static int info_only;
+static int force_remove;
+static int verbose;
+
+/* Three functions to allow overloaded pointer return; see linux/err.h */
+static inline void *ERR_PTR(long error)
+{
+ return (void *) error;
+}
+
+static inline long PTR_ERR(const void *ptr)
+{
+ return (long) ptr;
+}
+
+static inline long IS_ERR(const void *ptr)
+{
+ return (unsigned long)ptr > (unsigned long)-1000L;
+}
+
+static void report(const char *fmt, ...)
+{
+ va_list vp;
+
+ if (!verbose)
+ return;
+
+ va_start(vp, fmt);
+ vprintf(fmt, vp);
+ putchar('\n');
+ va_end(vp);
+}
+
+static int add_file_to_cache(const char *path)
+{
+ int size, namelen, option, status;
+ struct cache_entry *ce;
+ struct stat st;
+
+ status = lstat(path, &st);
+ if (status < 0 || S_ISDIR(st.st_mode)) {
+ /* When we used to have "path" and now we want to add
+ * "path/file", we need a way to remove "path" before
+ * being able to add "path/file". However,
+ * "git-update-index --remove path" would not work.
+ * --force-remove can be used but this is more user
+ * friendly, especially since we can do the opposite
+ * case just fine without --force-remove.
+ */
+ if (status == 0 || (errno == ENOENT || errno == ENOTDIR)) {
+ if (allow_remove) {
+ if (remove_file_from_cache(path))
+ return error("%s: cannot remove from the index",
+ path);
+ else
+ return 0;
+ } else if (status < 0) {
+ return error("%s: does not exist and --remove not passed",
+ path);
+ }
+ }
+ if (0 == status)
+ return error("%s: is a directory - add files inside instead",
+ path);
+ else
+ return error("lstat(\"%s\"): %s", path,
+ strerror(errno));
+ }
+
+ namelen = strlen(path);
+ size = cache_entry_size(namelen);
+ ce = xmalloc(size);
+ memset(ce, 0, size);
+ memcpy(ce->name, path, namelen);
+ fill_stat_cache_info(ce, &st);
+
+ ce->ce_mode = create_ce_mode(st.st_mode);
+ if (!trust_executable_bit) {
+ /* If there is an existing entry, pick the mode bits
+ * from it.
+ */
+ int pos = cache_name_pos(path, namelen);
+ if (0 <= pos)
+ ce->ce_mode = active_cache[pos]->ce_mode;
+ }
+ ce->ce_flags = htons(namelen);
+
+ if (index_path(ce->sha1, path, &st, !info_only))
+ return -1;
+ option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+ option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+ if (add_cache_entry(ce, option))
+ return error("%s: cannot add to the index - missing --add option?",
+ path);
+ return 0;
+}
+
+/*
+ * "refresh" does not calculate a new sha1 file or bring the
+ * cache up-to-date for mode/content changes. But what it
+ * _does_ do is to "re-match" the stat information of a file
+ * with the cache, so that you can refresh the cache for a
+ * file that hasn't been changed but where the stat entry is
+ * out of date.
+ *
+ * For example, you'd want to do this after doing a "git-read-tree",
+ * to link up the stat cache details with the proper files.
+ */
+static struct cache_entry *refresh_entry(struct cache_entry *ce)
+{
+ struct stat st;
+ struct cache_entry *updated;
+ int changed, size;
+
+ if (lstat(ce->name, &st) < 0)
+ return ERR_PTR(-errno);
+
+ changed = ce_match_stat(ce, &st);
+ if (!changed)
+ return NULL;
+
+ if (ce_modified(ce, &st))
+ return ERR_PTR(-EINVAL);
+
+ size = ce_size(ce);
+ updated = xmalloc(size);
+ memcpy(updated, ce, size);
+ fill_stat_cache_info(updated, &st);
+ return updated;
+}
+
+static int refresh_cache(void)
+{
+ int i;
+ int has_errors = 0;
+
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce, *new;
+ ce = active_cache[i];
+ if (ce_stage(ce)) {
+ while ((i < active_nr) &&
+ ! strcmp(active_cache[i]->name, ce->name))
+ i++;
+ i--;
+ if (allow_unmerged)
+ continue;
+ printf("%s: needs merge\n", ce->name);
+ has_errors = 1;
+ continue;
+ }
+
+ new = refresh_entry(ce);
+ if (!new)
+ continue;
+ if (IS_ERR(new)) {
+ if (not_new && PTR_ERR(new) == -ENOENT)
+ continue;
+ if (quiet)
+ continue;
+ printf("%s: needs update\n", ce->name);
+ has_errors = 1;
+ continue;
+ }
+ active_cache_changed = 1;
+ /* You can NOT just free active_cache[i] here, since it
+ * might not be necessarily malloc()ed but can also come
+ * from mmap(). */
+ active_cache[i] = new;
+ }
+ return has_errors;
+}
+
+/*
+ * We fundamentally don't like some paths: we don't want
+ * dot or dot-dot anywhere, and for obvious reasons don't
+ * want to recurse into ".git" either.
+ *
+ * Also, we don't want double slashes or slashes at the
+ * end that can make pathnames ambiguous.
+ */
+static int verify_dotfile(const char *rest)
+{
+ /*
+ * The first character was '.', but that
+ * has already been discarded, we now test
+ * the rest.
+ */
+ switch (*rest) {
+ /* "." is not allowed */
+ case '\0': case '/':
+ return 0;
+
+ /*
+ * ".git" followed by NUL or slash is bad. This
+ * shares the path end test with the ".." case.
+ */
+ case 'g':
+ if (rest[1] != 'i')
+ break;
+ if (rest[2] != 't')
+ break;
+ rest += 2;
+ /* fallthrough */
+ case '.':
+ if (rest[1] == '\0' || rest[1] == '/')
+ return 0;
+ }
+ return 1;
+}
+
+static int verify_path(const char *path)
+{
+ char c;
+
+ goto inside;
+ for (;;) {
+ if (!c)
+ return 1;
+ if (c == '/') {
+inside:
+ c = *path++;
+ switch (c) {
+ default:
+ continue;
+ case '/': case '\0':
+ break;
+ case '.':
+ if (verify_dotfile(path))
+ continue;
+ }
+ return 0;
+ }
+ c = *path++;
+ }
+}
+
+static int add_cacheinfo(const char *arg1, const char *arg2, const char *arg3)
+{
+ int size, len, option;
+ unsigned int mode;
+ unsigned char sha1[20];
+ struct cache_entry *ce;
+
+ if (sscanf(arg1, "%o", &mode) != 1)
+ return -1;
+ if (get_sha1_hex(arg2, sha1))
+ return -1;
+ if (!verify_path(arg3))
+ return -1;
+
+ len = strlen(arg3);
+ size = cache_entry_size(len);
+ ce = xmalloc(size);
+ memset(ce, 0, size);
+
+ memcpy(ce->sha1, sha1, 20);
+ memcpy(ce->name, arg3, len);
+ ce->ce_flags = htons(len);
+ ce->ce_mode = create_ce_mode(mode);
+ option = allow_add ? ADD_CACHE_OK_TO_ADD : 0;
+ option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0;
+ if (add_cache_entry(ce, option))
+ return error("%s: cannot add to the index - missing --add option?",
+ arg3);
+ report("add '%s'", arg3);
+ return 0;
+}
+
+static int chmod_path(int flip, const char *path)
+{
+ int pos;
+ struct cache_entry *ce;
+ unsigned int mode;
+
+ pos = cache_name_pos(path, strlen(path));
+ if (pos < 0)
+ return -1;
+ ce = active_cache[pos];
+ mode = ntohl(ce->ce_mode);
+ if (!S_ISREG(mode))
+ return -1;
+ switch (flip) {
+ case '+':
+ ce->ce_mode |= htonl(0111); break;
+ case '-':
+ ce->ce_mode &= htonl(~0111); break;
+ default:
+ return -1;
+ }
+ active_cache_changed = 1;
+ return 0;
+}
+
+static struct cache_file cache_file;
+
+static void update_one(const char *path, const char *prefix, int prefix_length)
+{
+ const char *p = prefix_path(prefix, prefix_length, path);
+ if (!verify_path(p)) {
+ fprintf(stderr, "Ignoring path %s\n", path);
+ return;
+ }
+ if (force_remove) {
+ if (remove_file_from_cache(p))
+ die("git-update-index: unable to remove %s", path);
+ report("remove '%s'", path);
+ return;
+ }
+ if (add_file_to_cache(p))
+ die("Unable to process file %s", path);
+ report("add '%s'", path);
+}
+
+static void read_index_info(int line_termination)
+{
+ struct strbuf buf;
+ strbuf_init(&buf);
+ while (1) {
+ char *ptr;
+ char *path_name;
+ unsigned char sha1[20];
+ unsigned int mode;
+
+ read_line(&buf, stdin, line_termination);
+ if (buf.eof)
+ break;
+
+ mode = strtoul(buf.buf, &ptr, 8);
+ if (ptr == buf.buf || *ptr != ' ' ||
+ get_sha1_hex(ptr + 1, sha1) ||
+ ptr[41] != '\t')
+ goto bad_line;
+
+ ptr += 42;
+
+ if (line_termination && ptr[0] == '"')
+ path_name = unquote_c_style(ptr, NULL);
+ else
+ path_name = ptr;
+
+ if (!verify_path(path_name)) {
+ fprintf(stderr, "Ignoring path %s\n", path_name);
+ if (path_name != ptr)
+ free(path_name);
+ continue;
+ }
+
+ if (!mode) {
+ /* mode == 0 means there is no such path -- remove */
+ if (remove_file_from_cache(path_name))
+ die("git-update-index: unable to remove %s",
+ ptr);
+ }
+ else {
+ /* mode ' ' sha1 '\t' name
+ * ptr[-1] points at tab,
+ * ptr[-41] is at the beginning of sha1
+ */
+ ptr[-42] = ptr[-1] = 0;
+ if (add_cacheinfo(buf.buf, ptr-41, path_name))
+ die("git-update-index: unable to update %s",
+ path_name);
+ }
+ if (path_name != ptr)
+ free(path_name);
+ continue;
+
+ bad_line:
+ die("malformed index info %s", buf.buf);
+ }
+}
+
+static const char update_index_usage[] =
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+
+int main(int argc, const char **argv)
+{
+ int i, newfd, entries, has_errors = 0, line_termination = '\n';
+ int allow_options = 1;
+ int read_from_stdin = 0;
+ const char *prefix = setup_git_directory();
+ int prefix_length = prefix ? strlen(prefix) : 0;
+
+ git_config(git_default_config);
+
+ newfd = hold_index_file_for_update(&cache_file, get_index_file());
+ if (newfd < 0)
+ die("unable to create new cachefile");
+
+ entries = read_cache();
+ if (entries < 0)
+ die("cache corrupted");
+
+ for (i = 1 ; i < argc; i++) {
+ const char *path = argv[i];
+
+ if (allow_options && *path == '-') {
+ if (!strcmp(path, "--")) {
+ allow_options = 0;
+ continue;
+ }
+ if (!strcmp(path, "-q")) {
+ quiet = 1;
+ continue;
+ }
+ if (!strcmp(path, "--add")) {
+ allow_add = 1;
+ continue;
+ }
+ if (!strcmp(path, "--replace")) {
+ allow_replace = 1;
+ continue;
+ }
+ if (!strcmp(path, "--remove")) {
+ allow_remove = 1;
+ continue;
+ }
+ if (!strcmp(path, "--unmerged")) {
+ allow_unmerged = 1;
+ continue;
+ }
+ if (!strcmp(path, "--refresh")) {
+ has_errors |= refresh_cache();
+ continue;
+ }
+ if (!strcmp(path, "--cacheinfo")) {
+ if (i+3 >= argc)
+ die("git-update-index: --cacheinfo <mode> <sha1> <path>");
+ if (add_cacheinfo(argv[i+1], argv[i+2], argv[i+3]))
+ die("git-update-index: --cacheinfo cannot add %s", argv[i+3]);
+ i += 3;
+ continue;
+ }
+ if (!strcmp(path, "--chmod=-x") ||
+ !strcmp(path, "--chmod=+x")) {
+ if (argc <= i+1)
+ die("git-update-index: %s <path>", path);
+ if (chmod_path(path[8], argv[++i]))
+ die("git-update-index: %s cannot chmod %s", path, argv[i]);
+ continue;
+ }
+ if (!strcmp(path, "--info-only")) {
+ info_only = 1;
+ continue;
+ }
+ if (!strcmp(path, "--force-remove")) {
+ force_remove = 1;
+ continue;
+ }
+ if (!strcmp(path, "-z")) {
+ line_termination = 0;
+ continue;
+ }
+ if (!strcmp(path, "--stdin")) {
+ if (i != argc - 1)
+ die("--stdin must be at the end");
+ read_from_stdin = 1;
+ break;
+ }
+ if (!strcmp(path, "--index-info")) {
+ allow_add = allow_replace = allow_remove = 1;
+ read_index_info(line_termination);
+ continue;
+ }
+ if (!strcmp(path, "--ignore-missing")) {
+ not_new = 1;
+ continue;
+ }
+ if (!strcmp(path, "--verbose")) {
+ verbose = 1;
+ continue;
+ }
+ if (!strcmp(path, "-h") || !strcmp(path, "--help"))
+ usage(update_index_usage);
+ die("unknown option %s", path);
+ }
+ update_one(path, prefix, prefix_length);
+ }
+ if (read_from_stdin) {
+ struct strbuf buf;
+ strbuf_init(&buf);
+ while (1) {
+ read_line(&buf, stdin, line_termination);
+ if (buf.eof)
+ break;
+ update_one(buf.buf, prefix, prefix_length);
+ }
+ }
+ if (active_cache_changed) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_index_file(&cache_file))
+ die("Unable to write new cachefile");
+ }
+
+ return has_errors ? 1 : 0;
+}
diff --git a/update-ref.c b/update-ref.c
new file mode 100644
index 000000000..e6fbddbab
--- /dev/null
+++ b/update-ref.c
@@ -0,0 +1,84 @@
+#include "cache.h"
+#include "refs.h"
+
+static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
+
+static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
+{
+ char buf[40];
+ int fd = open(path, O_RDONLY), nr;
+ if (fd < 0)
+ return -1;
+ nr = read(fd, buf, 40);
+ close(fd);
+ if (nr != 40 || get_sha1_hex(buf, currsha1) < 0)
+ return -1;
+ return memcmp(oldsha1, currsha1, 20) ? -1 : 0;
+}
+
+int main(int argc, char **argv)
+{
+ char *hex;
+ const char *refname, *value, *oldval, *path;
+ char *lockpath;
+ unsigned char sha1[20], oldsha1[20], currsha1[20];
+ int fd, written;
+
+ setup_git_directory();
+ if (argc < 3 || argc > 4)
+ usage(git_update_ref_usage);
+
+ refname = argv[1];
+ value = argv[2];
+ oldval = argv[3];
+ if (get_sha1(value, sha1) < 0)
+ die("%s: not a valid SHA1", value);
+ memset(oldsha1, 0, 20);
+ if (oldval && get_sha1(oldval, oldsha1) < 0)
+ die("%s: not a valid old SHA1", oldval);
+
+ path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
+ if (!path)
+ die("No such ref: %s", refname);
+
+ if (oldval) {
+ if (memcmp(currsha1, oldsha1, 20))
+ die("Ref %s is at %s but expected %s", refname, sha1_to_hex(currsha1), sha1_to_hex(oldsha1));
+ /* Nothing to do? */
+ if (!memcmp(oldsha1, sha1, 20))
+ exit(0);
+ }
+ path = strdup(path);
+ lockpath = mkpath("%s.lock", path);
+ if (safe_create_leading_directories(lockpath) < 0)
+ die("Unable to create all of %s", lockpath);
+
+ fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ if (fd < 0)
+ die("Unable to create %s", lockpath);
+ hex = sha1_to_hex(sha1);
+ hex[40] = '\n';
+ written = write(fd, hex, 41);
+ close(fd);
+ if (written != 41) {
+ unlink(lockpath);
+ die("Unable to write to %s", lockpath);
+ }
+
+ /*
+ * Re-read the ref after getting the lock to verify
+ */
+ if (oldval && re_verify(path, oldsha1, currsha1) < 0) {
+ unlink(lockpath);
+ die("Ref lock failed");
+ }
+
+ /*
+ * Finally, replace the old ref with the new one
+ */
+ if (rename(lockpath, path) < 0) {
+ unlink(lockpath);
+ die("Unable to create %s", path);
+ }
+ return 0;
+}
diff --git a/update-server-info.c b/update-server-info.c
new file mode 100644
index 000000000..e824f62ea
--- /dev/null
+++ b/update-server-info.c
@@ -0,0 +1,23 @@
+#include "cache.h"
+
+static const char update_server_info_usage[] =
+"git-update-server-info [--force]";
+
+int main(int ac, char **av)
+{
+ int i;
+ int force = 0;
+ for (i = 1; i < ac; i++) {
+ if (av[i][0] == '-') {
+ if (!strcmp("--force", av[i]) ||
+ !strcmp("-f", av[i]))
+ force = 1;
+ else
+ usage(update_server_info_usage);
+ }
+ }
+ if (i != ac)
+ usage(update_server_info_usage);
+
+ return !!update_server_info(force);
+}
diff --git a/upload-pack.c b/upload-pack.c
new file mode 100644
index 000000000..be6313280
--- /dev/null
+++ b/upload-pack.c
@@ -0,0 +1,292 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include "tag.h"
+#include "object.h"
+#include "commit.h"
+
+static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
+
+#define THEY_HAVE (1U << 0)
+#define OUR_REF (1U << 1)
+#define WANTED (1U << 2)
+#define MAX_HAS 256
+#define MAX_NEEDS 256
+static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0;
+static unsigned char has_sha1[MAX_HAS][20];
+static unsigned char needs_sha1[MAX_NEEDS][20];
+static unsigned int timeout = 0;
+
+static void reset_timeout(void)
+{
+ alarm(timeout);
+}
+
+static int strip(char *line, int len)
+{
+ if (len && line[len-1] == '\n')
+ line[--len] = 0;
+ return len;
+}
+
+static void create_pack_file(void)
+{
+ int fd[2];
+ pid_t pid;
+ int create_full_pack = (nr_our_refs == nr_needs && !nr_has);
+
+ if (pipe(fd) < 0)
+ die("git-upload-pack: unable to create pipe");
+ pid = fork();
+ if (pid < 0)
+ die("git-upload-pack: unable to fork git-rev-list");
+
+ if (!pid) {
+ int i;
+ int args;
+ char **argv;
+ char *buf;
+ char **p;
+
+ if (create_full_pack)
+ args = 10;
+ else
+ args = nr_has + nr_needs + 5;
+ argv = xmalloc(args * sizeof(char *));
+ buf = xmalloc(args * 45);
+ p = argv;
+
+ dup2(fd[1], 1);
+ close(0);
+ close(fd[0]);
+ close(fd[1]);
+ *p++ = "git-rev-list";
+ *p++ = "--objects";
+ if (create_full_pack || MAX_NEEDS <= nr_needs)
+ *p++ = "--all";
+ else {
+ for (i = 0; i < nr_needs; i++) {
+ *p++ = buf;
+ memcpy(buf, sha1_to_hex(needs_sha1[i]), 41);
+ buf += 41;
+ }
+ }
+ if (!create_full_pack)
+ for (i = 0; i < nr_has; i++) {
+ *p++ = buf;
+ *buf++ = '^';
+ memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
+ buf += 41;
+ }
+ *p++ = NULL;
+ execvp("git-rev-list", argv);
+ die("git-upload-pack: unable to exec git-rev-list");
+ }
+ dup2(fd[0], 0);
+ close(fd[0]);
+ close(fd[1]);
+ execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);
+ die("git-upload-pack: unable to exec git-pack-objects");
+}
+
+static int got_sha1(char *hex, unsigned char *sha1)
+{
+ if (get_sha1_hex(hex, sha1))
+ die("git-upload-pack: expected SHA1 object, got '%s'", hex);
+ if (!has_sha1_file(sha1))
+ return 0;
+ if (nr_has < MAX_HAS) {
+ struct object *o = lookup_object(sha1);
+ if (!(o && o->parsed))
+ o = parse_object(sha1);
+ if (!o)
+ die("oops (%s)", sha1_to_hex(sha1));
+ if (o->type == commit_type) {
+ struct commit_list *parents;
+ if (o->flags & THEY_HAVE)
+ return 0;
+ o->flags |= THEY_HAVE;
+ for (parents = ((struct commit*)o)->parents;
+ parents;
+ parents = parents->next)
+ parents->item->object.flags |= THEY_HAVE;
+ }
+ memcpy(has_sha1[nr_has++], sha1, 20);
+ }
+ return 1;
+}
+
+static int get_common_commits(void)
+{
+ static char line[1000];
+ unsigned char sha1[20], last_sha1[20];
+ int len;
+
+ track_object_refs = 0;
+ save_commit_buffer = 0;
+
+ for(;;) {
+ len = packet_read_line(0, line, sizeof(line));
+ reset_timeout();
+
+ if (!len) {
+ if (nr_has == 0 || multi_ack)
+ packet_write(1, "NAK\n");
+ continue;
+ }
+ len = strip(line, len);
+ if (!strncmp(line, "have ", 5)) {
+ if (got_sha1(line+5, sha1) &&
+ (multi_ack || nr_has == 1)) {
+ if (nr_has >= MAX_HAS)
+ multi_ack = 0;
+ packet_write(1, "ACK %s%s\n",
+ sha1_to_hex(sha1),
+ multi_ack ? " continue" : "");
+ if (multi_ack)
+ memcpy(last_sha1, sha1, 20);
+ }
+ continue;
+ }
+ if (!strcmp(line, "done")) {
+ if (nr_has > 0) {
+ if (multi_ack)
+ packet_write(1, "ACK %s\n",
+ sha1_to_hex(last_sha1));
+ return 0;
+ }
+ packet_write(1, "NAK\n");
+ return -1;
+ }
+ die("git-upload-pack: expected SHA1 list, got '%s'", line);
+ }
+}
+
+static int receive_needs(void)
+{
+ static char line[1000];
+ int len, needs;
+
+ needs = 0;
+ for (;;) {
+ struct object *o;
+ unsigned char dummy[20], *sha1_buf;
+ len = packet_read_line(0, line, sizeof(line));
+ reset_timeout();
+ if (!len)
+ return needs;
+
+ sha1_buf = dummy;
+ if (needs == MAX_NEEDS) {
+ fprintf(stderr,
+ "warning: supporting only a max of %d requests. "
+ "sending everything instead.\n",
+ MAX_NEEDS);
+ }
+ else if (needs < MAX_NEEDS)
+ sha1_buf = needs_sha1[needs];
+
+ if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf))
+ die("git-upload-pack: protocol error, "
+ "expected to get sha, not '%s'", line);
+ if (strstr(line+45, "multi_ack"))
+ multi_ack = 1;
+
+ /* We have sent all our refs already, and the other end
+ * should have chosen out of them; otherwise they are
+ * asking for nonsense.
+ *
+ * Hmph. We may later want to allow "want" line that
+ * asks for something like "master~10" (symbolic)...
+ * would it make sense? I don't know.
+ */
+ o = lookup_object(sha1_buf);
+ if (!o || !(o->flags & OUR_REF))
+ die("git-upload-pack: not our ref %s", line+5);
+ if (!(o->flags & WANTED)) {
+ o->flags |= WANTED;
+ needs++;
+ }
+ }
+}
+
+static int send_ref(const char *refname, const unsigned char *sha1)
+{
+ static char *capabilities = "multi_ack";
+ struct object *o = parse_object(sha1);
+
+ if (capabilities)
+ packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname,
+ 0, capabilities);
+ else
+ packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+ capabilities = NULL;
+ if (!(o->flags & OUR_REF)) {
+ o->flags |= OUR_REF;
+ nr_our_refs++;
+ }
+ if (o->type == tag_type) {
+ o = deref_tag(o, refname, 0);
+ packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
+ }
+ return 0;
+}
+
+static int upload_pack(void)
+{
+ reset_timeout();
+ head_ref(send_ref);
+ for_each_ref(send_ref);
+ packet_flush(1);
+ nr_needs = receive_needs();
+ if (!nr_needs)
+ return 0;
+ get_common_commits();
+ create_pack_file();
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const char *dir;
+ int i;
+ int strict = 0;
+
+ for (i = 1; i < argc; i++) {
+ char *arg = argv[i];
+
+ if (arg[0] != '-')
+ break;
+ if (!strcmp(arg, "--strict")) {
+ strict = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--timeout=", 10)) {
+ timeout = atoi(arg+10);
+ continue;
+ }
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ }
+
+ if (i != argc-1)
+ usage(upload_pack_usage);
+ dir = argv[i];
+
+ /* chdir to the directory. If that fails, try appending ".git" */
+ if (chdir(dir) < 0) {
+ if (strict || chdir(mkpath("%s.git", dir)) < 0)
+ die("git-upload-pack unable to chdir to %s", dir);
+ }
+ if (!strict)
+ chdir(".git");
+
+ if (access("objects", X_OK) || access("refs", X_OK))
+ die("git-upload-pack: %s doesn't seem to be a git archive", dir);
+
+ putenv("GIT_DIR=.");
+ upload_pack();
+ return 0;
+}
diff --git a/usage.c b/usage.c
new file mode 100644
index 000000000..dfa87fe11
--- /dev/null
+++ b/usage.c
@@ -0,0 +1,39 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+static void report(const char *prefix, const char *err, va_list params)
+{
+ fputs(prefix, stderr);
+ vfprintf(stderr, err, params);
+ fputs("\n", stderr);
+}
+
+void usage(const char *err)
+{
+ fprintf(stderr, "usage: %s\n", err);
+ exit(129);
+}
+
+void die(const char *err, ...)
+{
+ va_list params;
+
+ va_start(params, err);
+ report("fatal: ", err, params);
+ va_end(params);
+ exit(128);
+}
+
+int error(const char *err, ...)
+{
+ va_list params;
+
+ va_start(params, err);
+ report("error: ", err, params);
+ va_end(params);
+ return -1;
+}
diff --git a/var.c b/var.c
new file mode 100644
index 000000000..51cf86a58
--- /dev/null
+++ b/var.c
@@ -0,0 +1,76 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Eric Biederman, 2005
+ */
+#include "cache.h"
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+static const char var_usage[] = "git-var [-l | <variable>]";
+
+struct git_var {
+ const char *name;
+ char *(*read)(void);
+};
+static struct git_var git_vars[] = {
+ { "GIT_COMMITTER_IDENT", git_committer_info },
+ { "GIT_AUTHOR_IDENT", git_author_info },
+ { "", NULL },
+};
+
+static void list_vars(void)
+{
+ struct git_var *ptr;
+ for(ptr = git_vars; ptr->read; ptr++) {
+ printf("%s=%s\n", ptr->name, ptr->read());
+ }
+}
+
+static const char *read_var(const char *var)
+{
+ struct git_var *ptr;
+ const char *val;
+ val = NULL;
+ for(ptr = git_vars; ptr->read; ptr++) {
+ if (strcmp(var, ptr->name) == 0) {
+ val = ptr->read();
+ break;
+ }
+ }
+ return val;
+}
+
+static int show_config(const char *var, const char *value)
+{
+ if (value)
+ printf("%s=%s\n", var, value);
+ else
+ printf("%s\n", var);
+ return git_default_config(var, value);
+}
+
+int main(int argc, char **argv)
+{
+ const char *val;
+ if (argc != 2) {
+ usage(var_usage);
+ }
+ setup_ident();
+ val = NULL;
+
+ if (strcmp(argv[1], "-l") == 0) {
+ git_config(show_config);
+ list_vars();
+ return 0;
+ }
+ git_config(git_default_config);
+ val = read_var(argv[1]);
+ if (!val)
+ usage(var_usage);
+
+ printf("%s\n", val);
+
+ return 0;
+}
diff --git a/verify-pack.c b/verify-pack.c
new file mode 100644
index 000000000..c99db9dd7
--- /dev/null
+++ b/verify-pack.c
@@ -0,0 +1,57 @@
+#include "cache.h"
+#include "pack.h"
+
+static int verify_one_pack(char *arg, int verbose)
+{
+ int len = strlen(arg);
+ struct packed_git *g;
+
+ while (1) {
+ /* Should name foo.idx, but foo.pack may be named;
+ * convert it to foo.idx
+ */
+ if (!strcmp(arg + len - 5, ".pack")) {
+ strcpy(arg + len - 5, ".idx");
+ len--;
+ }
+ /* Should name foo.idx now */
+ if ((g = add_packed_git(arg, len, 1)))
+ break;
+ /* No? did you name just foo? */
+ strcpy(arg + len, ".idx");
+ len += 4;
+ if ((g = add_packed_git(arg, len, 1)))
+ break;
+ return error("packfile %s not found.", arg);
+ }
+ return verify_pack(g, verbose);
+}
+
+static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>...";
+
+int main(int ac, char **av)
+{
+ int errs = 0;
+ int verbose = 0;
+ int no_more_options = 0;
+
+ while (1 < ac) {
+ char path[PATH_MAX];
+
+ if (!no_more_options && av[1][0] == '-') {
+ if (!strcmp("-v", av[1]))
+ verbose = 1;
+ else if (!strcmp("--", av[1]))
+ no_more_options = 1;
+ else
+ usage(verify_pack_usage);
+ }
+ else {
+ strcpy(path, av[1]);
+ if (verify_one_pack(path, verbose))
+ errs++;
+ }
+ ac--; av++;
+ }
+ return !!errs;
+}
diff --git a/write-tree.c b/write-tree.c
new file mode 100644
index 000000000..2b2c6b77a
--- /dev/null
+++ b/write-tree.c
@@ -0,0 +1,152 @@
+/*
+ * GIT - The information manager from hell
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "cache.h"
+
+static int missing_ok = 0;
+
+static int check_valid_sha1(unsigned char *sha1)
+{
+ int ret;
+
+ /* If we were anal, we'd check that the sha1 of the contents actually matches */
+ ret = has_sha1_file(sha1);
+ if (ret == 0)
+ perror(sha1_file_name(sha1));
+ return ret ? 0 : -1;
+}
+
+static int write_tree(struct cache_entry **cachep, int maxentries, const char *base, int baselen, unsigned char *returnsha1)
+{
+ unsigned char subdir_sha1[20];
+ unsigned long size, offset;
+ char *buffer;
+ int nr;
+
+ /* Guess at some random initial size */
+ size = 8192;
+ buffer = xmalloc(size);
+ offset = 0;
+
+ nr = 0;
+ while (nr < maxentries) {
+ struct cache_entry *ce = cachep[nr];
+ const char *pathname = ce->name, *filename, *dirname;
+ int pathlen = ce_namelen(ce), entrylen;
+ unsigned char *sha1;
+ unsigned int mode;
+
+ /* Did we hit the end of the directory? Return how many we wrote */
+ if (baselen >= pathlen || memcmp(base, pathname, baselen))
+ break;
+
+ sha1 = ce->sha1;
+ mode = ntohl(ce->ce_mode);
+
+ /* Do we have _further_ subdirectories? */
+ filename = pathname + baselen;
+ dirname = strchr(filename, '/');
+ if (dirname) {
+ int subdir_written;
+
+ subdir_written = write_tree(cachep + nr, maxentries - nr, pathname, dirname-pathname+1, subdir_sha1);
+ nr += subdir_written;
+
+ /* Now we need to write out the directory entry into this tree.. */
+ mode = S_IFDIR;
+ pathlen = dirname - pathname;
+
+ /* ..but the directory entry doesn't count towards the total count */
+ nr--;
+ sha1 = subdir_sha1;
+ }
+
+ if (!missing_ok && check_valid_sha1(sha1) < 0)
+ exit(1);
+
+ entrylen = pathlen - baselen;
+ if (offset + entrylen + 100 > size) {
+ size = alloc_nr(offset + entrylen + 100);
+ buffer = xrealloc(buffer, size);
+ }
+ offset += sprintf(buffer + offset, "%o %.*s", mode, entrylen, filename);
+ buffer[offset++] = 0;
+ memcpy(buffer + offset, sha1, 20);
+ offset += 20;
+ nr++;
+ }
+
+ write_sha1_file(buffer, offset, "tree", returnsha1);
+ free(buffer);
+ return nr;
+}
+
+int main(int argc, char **argv)
+{
+ int i, funny;
+ int entries = read_cache();
+ unsigned char sha1[20];
+
+ if (argc == 2) {
+ if (!strcmp(argv[1], "--missing-ok"))
+ missing_ok = 1;
+ else
+ die("unknown option %s", argv[1]);
+ }
+
+ if (argc > 2)
+ die("too many options");
+
+ if (entries < 0)
+ die("git-write-tree: error reading cache");
+
+ /* Verify that the tree is merged */
+ funny = 0;
+ for (i = 0; i < entries; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (ntohs(ce->ce_flags) & ~CE_NAMEMASK) {
+ if (10 < ++funny) {
+ fprintf(stderr, "...\n");
+ break;
+ }
+ fprintf(stderr, "%s: unmerged (%s)\n", ce->name, sha1_to_hex(ce->sha1));
+ }
+ }
+ if (funny)
+ die("git-write-tree: not able to write tree");
+
+ /* Also verify that the cache does not have path and path/file
+ * at the same time. At this point we know the cache has only
+ * stage 0 entries.
+ */
+ funny = 0;
+ for (i = 0; i < entries - 1; i++) {
+ /* path/file always comes after path because of the way
+ * the cache is sorted. Also path can appear only once,
+ * which means conflicting one would immediately follow.
+ */
+ const char *this_name = active_cache[i]->name;
+ const char *next_name = active_cache[i+1]->name;
+ int this_len = strlen(this_name);
+ if (this_len < strlen(next_name) &&
+ strncmp(this_name, next_name, this_len) == 0 &&
+ next_name[this_len] == '/') {
+ if (10 < ++funny) {
+ fprintf(stderr, "...\n");
+ break;
+ }
+ fprintf(stderr, "You have both %s and %s\n",
+ this_name, next_name);
+ }
+ }
+ if (funny)
+ die("git-write-tree: not able to write tree");
+
+ /* Ok, write it out */
+ if (write_tree(active_cache, entries, "", 0, sha1) != entries)
+ die("git-write-tree: internal error");
+ printf("%s\n", sha1_to_hex(sha1));
+ return 0;
+}