hlibgit2 0.17.0.8 → 0.18.0.1
raw patch · 297 files changed
+47858/−16221 lines, 297 files
Files
- Bindings/Libgit2.hs +92/−38
- Bindings/Libgit2/Attr.hsc +14/−5
- Bindings/Libgit2/Blob.hsc +12/−3
- Bindings/Libgit2/Blob.hsc.helper.c +4/−3
- Bindings/Libgit2/Branch.hsc +17/−4
- Bindings/Libgit2/Checkout.hsc +107/−0
- Bindings/Libgit2/Clone.hsc +49/−0
- Bindings/Libgit2/Commit.hsc +8/−4
- Bindings/Libgit2/Commit.hsc.helper.c +4/−3
- Bindings/Libgit2/Common.hsc +27/−14
- Bindings/Libgit2/Config.hsc +77/−40
- Bindings/Libgit2/CredHelpers.hsc +17/−0
- Bindings/Libgit2/Diff.hsc +201/−84
- Bindings/Libgit2/Errors.hsc +35/−3
- Bindings/Libgit2/Graph.hsc +12/−0
- Bindings/Libgit2/Ignore.hsc +13/−0
- Bindings/Libgit2/Index.hsc +45/−17
- Bindings/Libgit2/Indexer.hsc +19/−16
- Bindings/Libgit2/Merge.hsc +3/−0
- Bindings/Libgit2/Message.hsc +10/−0
- Bindings/Libgit2/Net.hsc +13/−4
- Bindings/Libgit2/Notes.hsc +14/−10
- Bindings/Libgit2/Object.hsc +5/−1
- Bindings/Libgit2/Odb.hsc +8/−1
- Bindings/Libgit2/OdbBackend.hsc +51/−4
- Bindings/Libgit2/Oid.hsc +9/−8
- Bindings/Libgit2/Oid.hsc.helper.c +5/−0
- Bindings/Libgit2/Pack.hsc +24/−0
- Bindings/Libgit2/Push.hsc +25/−0
- Bindings/Libgit2/Refdb.hsc +19/−0
- Bindings/Libgit2/RefdbBackend.hsc +56/−0
- Bindings/Libgit2/Reflog.hsc +10/−6
- Bindings/Libgit2/Refs.hsc +31/−13
- Bindings/Libgit2/Refspec.hsc +5/−0
- Bindings/Libgit2/Remote.hsc +69/−8
- Bindings/Libgit2/Repository.hsc +98/−5
- Bindings/Libgit2/Reset.hsc +20/−0
- Bindings/Libgit2/Revparse.hsc +29/−0
- Bindings/Libgit2/Revwalk.hsc +3/−0
- Bindings/Libgit2/Signature.hsc +2/−0
- Bindings/Libgit2/Stash.hsc +30/−0
- Bindings/Libgit2/Status.hsc +44/−23
- Bindings/Libgit2/Strarray.hsc +18/−0
- Bindings/Libgit2/Submodule.hsc +76/−30
- Bindings/Libgit2/Tag.hsc +11/−3
- Bindings/Libgit2/Tag.hsc.helper.c +1/−1
- Bindings/Libgit2/Threads.hsc +3/−1
- Bindings/Libgit2/Trace.hsc +31/−0
- Bindings/Libgit2/Transport.hsc +174/−0
- Bindings/Libgit2/Tree.hsc +25/−16
- Bindings/Libgit2/Tree.hsc.helper.c +1/−1
- Bindings/Libgit2/Types.hsc +38/−13
- Bindings/Libgit2/Version.hsc +2/−0
- hlibgit2.cabal +60/−11
- libgit2/deps/http-parser/http_parser.c +2150/−1754
- libgit2/deps/http-parser/http_parser.h +106/−70
- libgit2/include/git2.h +14/−1
- libgit2/include/git2/attr.h +40/−23
- libgit2/include/git2/blob.h +78/−9
- libgit2/include/git2/branch.h +179/−49
- libgit2/include/git2/checkout.h +285/−0
- libgit2/include/git2/clone.h +107/−0
- libgit2/include/git2/commit.h +42/−20
- libgit2/include/git2/common.h +94/−8
- libgit2/include/git2/config.h +260/−62
- libgit2/include/git2/cred_helpers.h +53/−0
- libgit2/include/git2/diff.h +725/−138
- libgit2/include/git2/errors.h +51/−39
- libgit2/include/git2/graph.h +41/−0
- libgit2/include/git2/ignore.h +78/−0
- libgit2/include/git2/index.h +353/−95
- libgit2/include/git2/indexer.h +29/−60
- libgit2/include/git2/inttypes.h +4/−0
- libgit2/include/git2/merge.h +22/−2
- libgit2/include/git2/message.h +49/−0
- libgit2/include/git2/net.h +9/−7
- libgit2/include/git2/notes.h +107/−44
- libgit2/include/git2/object.h +35/−5
- libgit2/include/git2/odb.h +102/−22
- libgit2/include/git2/odb_backend.h +55/−6
- libgit2/include/git2/oid.h +55/−20
- libgit2/include/git2/pack.h +143/−0
- libgit2/include/git2/push.h +132/−0
- libgit2/include/git2/refdb.h +98/−0
- libgit2/include/git2/refdb_backend.h +110/−0
- libgit2/include/git2/reflog.h +61/−22
- libgit2/include/git2/refs.h +288/−137
- libgit2/include/git2/refspec.h +30/−2
- libgit2/include/git2/remote.h +261/−32
- libgit2/include/git2/repository.h +400/−24
- libgit2/include/git2/reset.h +85/−0
- libgit2/include/git2/revparse.h +80/−0
- libgit2/include/git2/revwalk.h +32/−17
- libgit2/include/git2/signature.h +23/−12
- libgit2/include/git2/stash.h +122/−0
- libgit2/include/git2/status.h +141/−63
- libgit2/include/git2/strarray.h +60/−0
- libgit2/include/git2/submodule.h +487/−46
- libgit2/include/git2/tag.h +85/−56
- libgit2/include/git2/threads.h +4/−2
- libgit2/include/git2/trace.h +68/−0
- libgit2/include/git2/transport.h +328/−0
- libgit2/include/git2/tree.h +174/−96
- libgit2/include/git2/types.h +30/−5
- libgit2/include/git2/version.h +3/−3
- libgit2/include/git2/windows.h +0/−59
- libgit2/src/attr.c +106/−50
- libgit2/src/attr.h +5/−16
- libgit2/src/attr_file.c +63/−45
- libgit2/src/attr_file.h +19/−13
- libgit2/src/attrcache.h +24/−0
- libgit2/src/blob.c +109/−27
- libgit2/src/blob.h +1/−1
- libgit2/src/branch.c +471/−96
- libgit2/src/branch.h +6/−6
- libgit2/src/bswap.h +1/−1
- libgit2/src/buf_text.c +291/−0
- libgit2/src/buf_text.h +122/−0
- libgit2/src/buffer.c +68/−52
- libgit2/src/buffer.h +62/−21
- libgit2/src/cache.c +22/−11
- libgit2/src/cache.h +1/−1
- libgit2/src/cc-compat.h +3/−1
- libgit2/src/checkout.c +1383/−0
- libgit2/src/checkout.h +24/−0
- libgit2/src/clone.c +466/−0
- libgit2/src/commit.c +79/−128
- libgit2/src/commit.h +3/−3
- libgit2/src/commit_list.c +194/−0
- libgit2/src/commit_list.h +49/−0
- libgit2/src/common.h +55/−18
- libgit2/src/compat/fnmatch.h +0/−27
- libgit2/src/compress.c +53/−0
- libgit2/src/compress.h +16/−0
- libgit2/src/config.c +530/−229
- libgit2/src/config.h +22/−6
- libgit2/src/config_cache.c +2/−2
- libgit2/src/config_file.c +270/−108
- libgit2/src/config_file.h +36/−7
- libgit2/src/crlf.c +132/−44
- libgit2/src/date.c +876/−0
- libgit2/src/delta-apply.c +14/−1
- libgit2/src/delta-apply.h +18/−1
- libgit2/src/delta.c +424/−0
- libgit2/src/delta.h +114/−0
- libgit2/src/diff.c +532/−380
- libgit2/src/diff.h +41/−3
- libgit2/src/diff_output.c +1815/−782
- libgit2/src/diff_output.h +93/−0
- libgit2/src/diff_tform.c +687/−0
- libgit2/src/errors.c +37/−37
- libgit2/src/fetch.c +41/−115
- libgit2/src/fetch.h +13/−4
- libgit2/src/fetchhead.c +295/−0
- libgit2/src/fetchhead.h +34/−0
- libgit2/src/filebuf.c +62/−19
- libgit2/src/filebuf.h +5/−2
- libgit2/src/fileops.c +727/−187
- libgit2/src/fileops.h +237/−21
- libgit2/src/filter.c +7/−78
- libgit2/src/filter.h +3/−28
- libgit2/src/fnmatch.c +192/−0
- libgit2/src/fnmatch.h +28/−0
- libgit2/src/global.c +63/−10
- libgit2/src/global.h +12/−7
- libgit2/src/graph.c +178/−0
- libgit2/src/hash.c +25/−52
- libgit2/src/hash.h +21/−9
- libgit2/src/hash/hash_win32.c +291/−0
- libgit2/src/hashsig.c +368/−0
- libgit2/src/hashsig.h +72/−0
- libgit2/src/ignore.c +171/−14
- libgit2/src/ignore.h +7/−1
- libgit2/src/index.c +1679/−967
- libgit2/src/index.h +20/−5
- libgit2/src/indexer.c +295/−438
- libgit2/src/iterator.c +1348/−747
- libgit2/src/iterator.h +140/−86
- libgit2/src/khash.h +6/−4
- libgit2/src/map.h +5/−1
- libgit2/src/merge.c +296/−0
- libgit2/src/merge.h +22/−0
- libgit2/src/message.c +28/−3
- libgit2/src/message.h +3/−2
- libgit2/src/mwindow.c +78/−31
- libgit2/src/mwindow.h +2/−2
- libgit2/src/netops.c +469/−47
- libgit2/src/netops.h +55/−10
- libgit2/src/notes.c +395/−282
- libgit2/src/notes.h +2/−1
- libgit2/src/object.c +142/−71
- libgit2/src/object.h +32/−0
- libgit2/src/odb.c +243/−49
- libgit2/src/odb.h +25/−6
- libgit2/src/odb_loose.c +91/−6
- libgit2/src/odb_pack.c +219/−87
- libgit2/src/offmap.h +65/−0
- libgit2/src/oid.c +13/−13
- libgit2/src/oidmap.h +2/−7
- libgit2/src/pack-objects.c +1342/−0
- libgit2/src/pack-objects.h +87/−0
- libgit2/src/pack.c +365/−31
- libgit2/src/pack.h +54/−4
- libgit2/src/path.c +291/−42
- libgit2/src/path.h +83/−7
- libgit2/src/pathspec.c +168/−0
- libgit2/src/pathspec.h +40/−0
- libgit2/src/pkt.c +0/−361
- libgit2/src/pkt.h +0/−81
- libgit2/src/pool.c +36/−4
- libgit2/src/pool.h +26/−1
- libgit2/src/posix.c +94/−4
- libgit2/src/posix.h +55/−4
- libgit2/src/pqueue.c +23/−1
- libgit2/src/pqueue.h +23/−1
- libgit2/src/protocol.c +0/−58
- libgit2/src/protocol.h +0/−23
- libgit2/src/push.c +653/−0
- libgit2/src/push.h +51/−0
- libgit2/src/refdb.c +185/−0
- libgit2/src/refdb.h +46/−0
- libgit2/src/refdb_fs.c +1023/−0
- libgit2/src/refdb_fs.h +15/−0
- libgit2/src/reflog.c +300/−119
- libgit2/src/reflog.h +2/−1
- libgit2/src/refs.c +964/−1706
- libgit2/src/refs.h +26/−14
- libgit2/src/refspec.c +170/−26
- libgit2/src/refspec.h +30/−1
- libgit2/src/remote.c +1389/−508
- libgit2/src/remote.h +18/−3
- libgit2/src/repo_template.h +58/−0
- libgit2/src/repository.c +1735/−955
- libgit2/src/repository.h +28/−18
- libgit2/src/reset.c +163/−0
- libgit2/src/revparse.c +912/−0
- libgit2/src/revwalk.c +102/−413
- libgit2/src/revwalk.h +44/−0
- libgit2/src/sha1.c +0/−280
- libgit2/src/sha1.h +0/−21
- libgit2/src/sha1_lookup.c +1/−1
- libgit2/src/sha1_lookup.h +1/−1
- libgit2/src/signature.c +76/−204
- libgit2/src/signature.h +1/−1
- libgit2/src/stash.c +663/−0
- libgit2/src/status.c +145/−71
- libgit2/src/strmap.h +2/−2
- libgit2/src/submodule.c +1509/−385
- libgit2/src/submodule.h +102/−0
- libgit2/src/tag.c +75/−60
- libgit2/src/tag.h +1/−1
- libgit2/src/thread-utils.c +1/−1
- libgit2/src/thread-utils.h +9/−9
- libgit2/src/trace.c +39/−0
- libgit2/src/trace.h +56/−0
- libgit2/src/transport.c +80/−33
- libgit2/src/transport.h +0/−108
- libgit2/src/transports/cred.c +60/−0
- libgit2/src/transports/cred_helpers.c +49/−0
- libgit2/src/transports/git.c +197/−323
- libgit2/src/transports/http.c +952/−681
- libgit2/src/transports/local.c +457/−57
- libgit2/src/transports/smart.c +345/−0
- libgit2/src/transports/smart_pkt.c +546/−0
- libgit2/src/transports/smart_protocol.c +856/−0
- libgit2/src/transports/winhttp.c +1136/−0
- libgit2/src/tree-cache.c +3/−3
- libgit2/src/tree-cache.h +1/−1
- libgit2/src/tree.c +400/−250
- libgit2/src/tree.h +18/−5
- libgit2/src/tsort.c +39/−23
- libgit2/src/unix/map.c +5/−1
- libgit2/src/unix/posix.h +13/−9
- libgit2/src/unix/realpath.c +30/−0
- libgit2/src/util.c +286/−14
- libgit2/src/util.h +103/−15
- libgit2/src/vector.c +136/−57
- libgit2/src/vector.h +27/−24
- libgit2/src/win32/dir.c +9/−22
- libgit2/src/win32/dir.h +1/−1
- libgit2/src/win32/error.c +77/−0
- libgit2/src/win32/error.h +13/−0
- libgit2/src/win32/findfile.c +238/−0
- libgit2/src/win32/findfile.h +27/−0
- libgit2/src/win32/map.c +1/−1
- libgit2/src/win32/mingw-compat.h +1/−1
- libgit2/src/win32/msvc-compat.h +11/−1
- libgit2/src/win32/posix.h +14/−8
- libgit2/src/win32/posix_w32.c +255/−152
- libgit2/src/win32/precompiled.c +1/−0
- libgit2/src/win32/precompiled.h +19/−0
- libgit2/src/win32/pthread.c +69/−1
- libgit2/src/win32/pthread.h +8/−1
- libgit2/src/win32/utf-conv.c +56/−70
- libgit2/src/win32/utf-conv.h +5/−4
- libgit2/src/win32/version.h +20/−0
- test/Main.hs +2/−0
Bindings/Libgit2.hs view
@@ -1,44 +1,98 @@--module Bindings.Libgit2 (- module Bindings.Libgit2.Repository,- module Bindings.Libgit2.Config,- module Bindings.Libgit2.Types,- module Bindings.Libgit2.Revwalk,- module Bindings.Libgit2.OdbBackend,- module Bindings.Libgit2.Tag,- module Bindings.Libgit2.Net,- module Bindings.Libgit2.Refs,- module Bindings.Libgit2.Refspec,- module Bindings.Libgit2.Reflog,- module Bindings.Libgit2.Commit,- module Bindings.Libgit2.Index,- module Bindings.Libgit2.Signature,- module Bindings.Libgit2.Blob,- module Bindings.Libgit2.Errors,- module Bindings.Libgit2.Oid,- module Bindings.Libgit2.Tree,- module Bindings.Libgit2.Odb,- module Bindings.Libgit2.Common,- module Bindings.Libgit2.Remote,- module Bindings.Libgit2.Object) where-import Bindings.Libgit2.Repository+module Bindings.Libgit2+ ( module Bindings.Libgit2.Attr+ , module Bindings.Libgit2.Blob+ , module Bindings.Libgit2.Branch+ , module Bindings.Libgit2.Checkout+ , module Bindings.Libgit2.Clone+ , module Bindings.Libgit2.Commit+ , module Bindings.Libgit2.Common+ , module Bindings.Libgit2.Config+ , module Bindings.Libgit2.CredHelpers+ , module Bindings.Libgit2.Diff+ , module Bindings.Libgit2.Errors+ , module Bindings.Libgit2.Graph+ , module Bindings.Libgit2.Ignore+ , module Bindings.Libgit2.Index+ , module Bindings.Libgit2.Indexer+ , module Bindings.Libgit2.Merge+ , module Bindings.Libgit2.Message+ , module Bindings.Libgit2.Net+ , module Bindings.Libgit2.Notes+ , module Bindings.Libgit2.Object+ , module Bindings.Libgit2.Odb+ , module Bindings.Libgit2.OdbBackend+ , module Bindings.Libgit2.Oid+ , module Bindings.Libgit2.Pack+ , module Bindings.Libgit2.Push+ , module Bindings.Libgit2.Refdb+ , module Bindings.Libgit2.RefdbBackend+ , module Bindings.Libgit2.Reflog+ , module Bindings.Libgit2.Refs+ , module Bindings.Libgit2.Refspec+ , module Bindings.Libgit2.Remote+ , module Bindings.Libgit2.Repository+ , module Bindings.Libgit2.Reset+ , module Bindings.Libgit2.Revparse+ , module Bindings.Libgit2.Revwalk+ , module Bindings.Libgit2.Signature+ , module Bindings.Libgit2.Stash+ , module Bindings.Libgit2.Status+ , module Bindings.Libgit2.Strarray+ , module Bindings.Libgit2.Submodule+ , module Bindings.Libgit2.Tag+ , module Bindings.Libgit2.Threads+ , module Bindings.Libgit2.Trace+ , module Bindings.Libgit2.Transport+ , module Bindings.Libgit2.Tree+ , module Bindings.Libgit2.Types+ , module Bindings.Libgit2.Version+ , module Bindings.Libgit2.Windows+ ) where+import Bindings.Libgit2.Attr+import Bindings.Libgit2.Blob+import Bindings.Libgit2.Branch+import Bindings.Libgit2.Checkout+import Bindings.Libgit2.Clone+import Bindings.Libgit2.Commit+import Bindings.Libgit2.Common import Bindings.Libgit2.Config-import Bindings.Libgit2.Types-import Bindings.Libgit2.Revwalk-import Bindings.Libgit2.OdbBackend-import Bindings.Libgit2.Tag+import Bindings.Libgit2.CredHelpers+import Bindings.Libgit2.Diff+import Bindings.Libgit2.Errors+import Bindings.Libgit2.Graph+import Bindings.Libgit2.Ignore+import Bindings.Libgit2.Index+import Bindings.Libgit2.Indexer+import Bindings.Libgit2.Merge+import Bindings.Libgit2.Message import Bindings.Libgit2.Net+import Bindings.Libgit2.Notes+import Bindings.Libgit2.Object+import Bindings.Libgit2.Odb+import Bindings.Libgit2.OdbBackend+import Bindings.Libgit2.Oid+import Bindings.Libgit2.Pack+import Bindings.Libgit2.Push+import Bindings.Libgit2.Refdb+import Bindings.Libgit2.RefdbBackend+import Bindings.Libgit2.Reflog import Bindings.Libgit2.Refs import Bindings.Libgit2.Refspec-import Bindings.Libgit2.Reflog-import Bindings.Libgit2.Commit-import Bindings.Libgit2.Index+import Bindings.Libgit2.Remote+import Bindings.Libgit2.Repository+import Bindings.Libgit2.Reset+import Bindings.Libgit2.Revparse+import Bindings.Libgit2.Revwalk import Bindings.Libgit2.Signature-import Bindings.Libgit2.Blob-import Bindings.Libgit2.Errors-import Bindings.Libgit2.Oid+import Bindings.Libgit2.Stash+import Bindings.Libgit2.Status+import Bindings.Libgit2.Strarray+import Bindings.Libgit2.Submodule+import Bindings.Libgit2.Tag+import Bindings.Libgit2.Threads+import Bindings.Libgit2.Trace+import Bindings.Libgit2.Transport import Bindings.Libgit2.Tree-import Bindings.Libgit2.Odb-import Bindings.Libgit2.Common-import Bindings.Libgit2.Remote-import Bindings.Libgit2.Object+import Bindings.Libgit2.Types+import Bindings.Libgit2.Version+import Bindings.Libgit2.Windows
Bindings/Libgit2/Attr.hsc view
@@ -1,16 +1,25 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> #include <git2/attr.h> module Bindings.Libgit2.Attr where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types--{- extern __attribute__((visibility("default"))) const char * git_attr__true; -}-{- extern __attribute__((visibility("default"))) const char * git_attr__false; -}-{- extern __attribute__((visibility("default"))) const char * git_attr__unset; -}-+{- typedef enum {+ GIT_ATTR_UNSPECIFIED_T = 0,+ GIT_ATTR_TRUE_T,+ GIT_ATTR_FALSE_T,+ GIT_ATTR_VALUE_T+ } git_attr_t; -}+#integral_t git_attr_t+#num GIT_ATTR_UNSPECIFIED_T+#num GIT_ATTR_TRUE_T+#num GIT_ATTR_FALSE_T+#num GIT_ATTR_VALUE_T+#ccall git_attr_value , CString -> IO (<git_attr_t>) #ccall git_attr_get , Ptr (CString) -> Ptr <git_repository> -> CUInt -> CString -> CString -> IO (CInt) #ccall git_attr_get_many , Ptr (CString) -> Ptr <git_repository> -> CUInt -> CString -> CSize -> Ptr (CString) -> IO (CInt) #callback git_attr_foreach_callback , CString -> CString -> Ptr () -> IO CInt
Bindings/Libgit2/Blob.hsc view
@@ -1,6 +1,8 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Blob where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common@@ -8,10 +10,17 @@ import Bindings.Libgit2.Oid import Bindings.Libgit2.Object #cinline git_blob_lookup , Ptr (Ptr <git_blob>) -> Ptr <git_repository> -> Ptr <git_oid> -> IO (CInt)-#cinline git_blob_lookup_prefix , Ptr (Ptr <git_blob>) -> Ptr <git_repository> -> Ptr <git_oid> -> CUInt -> IO (CInt)+#cinline git_blob_lookup_prefix , Ptr (Ptr <git_blob>) -> Ptr <git_repository> -> Ptr <git_oid> -> CSize -> IO (CInt) #cinline git_blob_free , Ptr <git_blob> -> IO ()+#cinline git_blob_id , Ptr <git_blob> -> IO (Ptr <git_oid>) #ccall git_blob_rawcontent , Ptr <git_blob> -> IO (Ptr ())-#ccall git_blob_rawsize , Ptr <git_blob> -> IO (CSize)-#ccall git_blob_create_fromfile , Ptr <git_oid> -> Ptr <git_repository> -> CString -> IO (CInt)+#ccall git_blob_rawsize , Ptr <git_blob> -> IO (CLong)+#ccall git_blob_create_fromworkdir , Ptr <git_oid> -> Ptr <git_repository> -> CString -> IO (CInt) #ccall git_blob_create_fromdisk , Ptr <git_oid> -> Ptr <git_repository> -> CString -> IO (CInt)+{- typedef int (* git_blob_chunk_cb)(char * content,+ size_t max_length,+ void * payload); -}+#callback git_blob_chunk_cb , Ptr Char -> CSize -> Ptr () -> IO CInt+#ccall git_blob_create_fromchunks , Ptr <git_oid> -> Ptr <git_repository> -> CString -> <git_blob_chunk_cb> -> Ptr () -> IO (CInt) #ccall git_blob_create_frombuffer , Ptr <git_oid> -> Ptr <git_repository> -> Ptr () -> CSize -> IO (CInt)+#ccall git_blob_is_binary , Ptr <git_blob> -> IO (CInt)
Bindings/Libgit2/Blob.hsc.helper.c view
@@ -1,6 +1,7 @@ #include <bindings.cmacros.h> #include <git2.h> -BC_INLINE3(git_blob_lookup, git_blob * *, git_repository *, const git_oid *, int)-BC_INLINE4(git_blob_lookup_prefix, git_blob * *, git_repository *, const git_oid *, unsigned int, int)-BC_INLINE1VOID(git_blob_free, git_blob *)+BC_INLINE3(git_blob_lookup, git_blob**, git_repository*, const git_oid*, int)+BC_INLINE4(git_blob_lookup_prefix, git_blob**, git_repository*, const git_oid*, size_t, int)+BC_INLINE1VOID(git_blob_free, git_blob*)+BC_INLINE1(git_blob_id, const git_blob*, const git_oid*)
Bindings/Libgit2/Branch.hsc view
@@ -1,12 +1,25 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Branch where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Oid import Bindings.Libgit2.Types-#ccall git_branch_create , Ptr <git_oid> -> Ptr <git_repository> -> CString -> Ptr <git_object> -> CInt -> IO (CInt)-#ccall git_branch_delete , Ptr <git_repository> -> CString -> <git_branch_t> -> IO (CInt)-#ccall git_branch_list , Ptr <git_strarray> -> Ptr <git_repository> -> CUInt -> IO (CInt)-#ccall git_branch_move , Ptr <git_repository> -> CString -> CString -> CInt -> IO (CInt)+#ccall git_branch_create , Ptr (Ptr <git_reference>) -> Ptr <git_repository> -> CString -> Ptr <git_commit> -> CInt -> IO (CInt)+#ccall git_branch_delete , Ptr <git_reference> -> IO (CInt)+{- typedef int (* git_branch_foreach_cb)(const char * branch_name,+ git_branch_t branch_type,+ void * payload); -}+#callback git_branch_foreach_cb , CString -> <git_branch_t> -> Ptr () -> IO (CInt)+#ccall git_branch_foreach , Ptr <git_repository> -> CUInt -> <git_branch_foreach_cb> -> Ptr () -> IO (CInt)+#ccall git_branch_move , Ptr (Ptr <git_reference>) -> Ptr <git_reference> -> CString -> CInt -> IO (CInt)+#ccall git_branch_lookup , Ptr (Ptr <git_reference>) -> Ptr <git_repository> -> CString -> <git_branch_t> -> IO (CInt)+#ccall git_branch_name , Ptr (CString) -> Ptr <git_reference> -> IO (CInt)+#ccall git_branch_upstream , Ptr (Ptr <git_reference>) -> Ptr <git_reference> -> IO (CInt)+#ccall git_branch_set_upstream , Ptr <git_reference> -> CString -> IO (CInt)+#ccall git_branch_upstream_name , CString -> CSize -> Ptr <git_repository> -> CString -> IO (CInt)+#ccall git_branch_is_head , Ptr <git_reference> -> IO (CInt)+#ccall git_branch_remote_name , CString -> CSize -> Ptr <git_repository> -> CString -> IO (CInt)
+ Bindings/Libgit2/Checkout.hsc view
@@ -0,0 +1,107 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/checkout.h>+module Bindings.Libgit2.Checkout where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+import Bindings.Libgit2.Strarray+import Bindings.Libgit2.Diff+{- typedef enum {+ GIT_CHECKOUT_NONE = 0,+ GIT_CHECKOUT_SAFE = 1u << 0,+ GIT_CHECKOUT_SAFE_CREATE = 1u << 1,+ GIT_CHECKOUT_FORCE = 1u << 2,+ GIT_CHECKOUT_ALLOW_CONFLICTS = 1u << 4,+ GIT_CHECKOUT_REMOVE_UNTRACKED = 1u << 5,+ GIT_CHECKOUT_REMOVE_IGNORED = 1u << 6,+ GIT_CHECKOUT_UPDATE_ONLY = 1u << 7,+ GIT_CHECKOUT_DONT_UPDATE_INDEX = 1u << 8,+ GIT_CHECKOUT_NO_REFRESH = 1u << 9,+ GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = 1u << 13,+ GIT_CHECKOUT_SKIP_UNMERGED = 1u << 10,+ GIT_CHECKOUT_USE_OURS = 1u << 11,+ GIT_CHECKOUT_USE_THEIRS = 1u << 12,+ GIT_CHECKOUT_UPDATE_SUBMODULES = 1u << 16,+ GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = 1u << 17+ } git_checkout_strategy_t; -}+#integral_t git_checkout_strategy_t+#num GIT_CHECKOUT_NONE+#num GIT_CHECKOUT_SAFE+#num GIT_CHECKOUT_SAFE_CREATE+#num GIT_CHECKOUT_FORCE+#num GIT_CHECKOUT_ALLOW_CONFLICTS+#num GIT_CHECKOUT_REMOVE_UNTRACKED+#num GIT_CHECKOUT_REMOVE_IGNORED+#num GIT_CHECKOUT_UPDATE_ONLY+#num GIT_CHECKOUT_DONT_UPDATE_INDEX+#num GIT_CHECKOUT_NO_REFRESH+#num GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH+#num GIT_CHECKOUT_SKIP_UNMERGED+#num GIT_CHECKOUT_USE_OURS+#num GIT_CHECKOUT_USE_THEIRS+#num GIT_CHECKOUT_UPDATE_SUBMODULES+#num GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED+{- typedef enum {+ GIT_CHECKOUT_NOTIFY_NONE = 0,+ GIT_CHECKOUT_NOTIFY_CONFLICT = 1u << 0,+ GIT_CHECKOUT_NOTIFY_DIRTY = 1u << 1,+ GIT_CHECKOUT_NOTIFY_UPDATED = 1u << 2,+ GIT_CHECKOUT_NOTIFY_UNTRACKED = 1u << 3,+ GIT_CHECKOUT_NOTIFY_IGNORED = 1u << 4+ } git_checkout_notify_t; -}+#integral_t git_checkout_notify_t+#num GIT_CHECKOUT_NOTIFY_NONE+#num GIT_CHECKOUT_NOTIFY_CONFLICT+#num GIT_CHECKOUT_NOTIFY_DIRTY+#num GIT_CHECKOUT_NOTIFY_UPDATED+#num GIT_CHECKOUT_NOTIFY_UNTRACKED+#num GIT_CHECKOUT_NOTIFY_IGNORED+{- typedef int (* git_checkout_notify_cb)(git_checkout_notify_t why,+ const char * path,+ const git_diff_file * baseline,+ const git_diff_file * target,+ const git_diff_file * workdir,+ void * payload); -}+#callback git_checkout_notify_cb , <git_checkout_notify_t> -> CString -> Ptr (<git_diff_file>) -> Ptr (<git_diff_file>) -> Ptr (<git_diff_file>) -> Ptr () -> IO CInt+{- typedef void (* git_checkout_progress_cb)(const char * path,+ size_t completed_steps,+ size_t total_steps,+ void * payload); -}+#callback git_checkout_progress_cb , CString -> CSize -> CSize -> Ptr () -> IO ()+{- typedef struct git_checkout_opts {+ unsigned int version;+ unsigned int checkout_strategy;+ int disable_filters;+ unsigned int dir_mode;+ unsigned int file_mode;+ int file_open_flags;+ unsigned int notify_flags;+ git_checkout_notify_cb notify_cb;+ void * notify_payload;+ git_checkout_progress_cb progress_cb;+ void * progress_payload;+ git_strarray paths;+ git_tree * baseline;+ } git_checkout_opts; -}+#starttype git_checkout_opts+#field version , CUInt+#field checkout_strategy , CUInt+#field disable_filters , CInt+#field dir_mode , CUInt+#field file_mode , CUInt+#field file_open_flags , CInt+#field notify_flags , CUInt+#field notify_cb , <git_checkout_notify_cb>+#field notify_payload , Ptr ()+#field progress_cb , <git_checkout_progress_cb>+#field progress_payload , Ptr ()+#field paths , <git_strarray>+#field baseline , Ptr <git_tree>+#stoptype+#ccall git_checkout_head , Ptr <git_repository> -> Ptr <git_checkout_opts> -> IO (CInt)+#ccall git_checkout_index , Ptr <git_repository> -> Ptr <git_index> -> Ptr <git_checkout_opts> -> IO (CInt)+#ccall git_checkout_tree , Ptr <git_repository> -> Ptr <git_object> -> Ptr <git_checkout_opts> -> IO (CInt)
+ Bindings/Libgit2/Clone.hsc view
@@ -0,0 +1,49 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/clone.h>+module Bindings.Libgit2.Clone where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+import Bindings.Libgit2.Indexer+import Bindings.Libgit2.Checkout+import Bindings.Libgit2.Remote+import Bindings.Libgit2.Transport+{- typedef struct git_clone_options {+ unsigned int version;+ git_checkout_opts checkout_opts;+ int bare;+ git_transfer_progress_callback fetch_progress_cb;+ void * fetch_progress_payload;+ const char * remote_name;+ const char * pushurl;+ const char * fetch_spec;+ const char * push_spec;+ git_cred_acquire_cb cred_acquire_cb;+ void * cred_acquire_payload;+ git_transport * transport;+ git_remote_callbacks * remote_callbacks;+ git_remote_autotag_option_t remote_autotag;+ const char * checkout_branch;+ } git_clone_options; -}+#starttype git_clone_options+#field version , CUInt+#field checkout_opts , <git_checkout_opts>+#field bare , CInt+#field fetch_progress_cb , CInt+#field fetch_progress_payload , Ptr ()+#field remote_name , CString+#field pushurl , CString+#field fetch_spec , CString+#field push_spec , CString+#field cred_acquire_cb , CInt+#field cred_acquire_payload , Ptr ()+#field transport , Ptr <git_transport>+#field remote_callbacks , Ptr <git_remote_callbacks>+#field remote_autotag , <git_remote_autotag_option_t>+#field checkout_branch , CString+#stoptype+#ccall git_clone , Ptr (Ptr <git_repository>) -> CString -> CString -> Ptr <git_clone_options> -> IO (CInt)
Bindings/Libgit2/Commit.hsc view
@@ -1,6 +1,8 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Commit where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common@@ -8,9 +10,9 @@ import Bindings.Libgit2.Oid import Bindings.Libgit2.Object #cinline git_commit_lookup , Ptr (Ptr <git_commit>) -> Ptr <git_repository> -> Ptr <git_oid> -> IO (CInt)-#cinline git_commit_lookup_prefix , Ptr (Ptr <git_commit>) -> Ptr <git_repository> -> Ptr <git_oid> -> CUInt -> IO (CInt)+#cinline git_commit_lookup_prefix , Ptr (Ptr <git_commit>) -> Ptr <git_repository> -> Ptr <git_oid> -> CSize -> IO (CInt) #cinline git_commit_free , Ptr <git_commit> -> IO ()-#ccall git_commit_id , Ptr <git_commit> -> IO (Ptr <git_oid>)+#cinline git_commit_id , Ptr <git_commit> -> IO (Ptr <git_oid>) #ccall git_commit_message_encoding , Ptr <git_commit> -> IO (CString) #ccall git_commit_message , Ptr <git_commit> -> IO (CString) #ccall git_commit_time , Ptr <git_commit> -> IO (CLong)@@ -18,9 +20,11 @@ #ccall git_commit_committer , Ptr <git_commit> -> IO (Ptr <git_signature>) #ccall git_commit_author , Ptr <git_commit> -> IO (Ptr <git_signature>) #ccall git_commit_tree , Ptr (Ptr <git_tree>) -> Ptr <git_commit> -> IO (CInt)-#ccall git_commit_tree_oid , Ptr <git_commit> -> IO (Ptr <git_oid>)+#ccall git_commit_tree_id , Ptr <git_commit> -> IO (Ptr <git_oid>) #ccall git_commit_parentcount , Ptr <git_commit> -> IO (CUInt) #ccall git_commit_parent , Ptr (Ptr <git_commit>) -> Ptr <git_commit> -> CUInt -> IO (CInt)-#ccall git_commit_parent_oid , Ptr <git_commit> -> CUInt -> IO (Ptr <git_oid>)+#ccall git_commit_parent_id , Ptr <git_commit> -> CUInt -> IO (Ptr <git_oid>)+#ccall git_commit_nth_gen_ancestor , Ptr (Ptr <git_commit>) -> Ptr <git_commit> -> CUInt -> IO (CInt) #ccall git_commit_create , Ptr <git_oid> -> Ptr <git_repository> -> CString -> Ptr <git_signature> -> Ptr <git_signature> -> CString -> CString -> Ptr <git_tree> -> CInt -> Ptr (Ptr <git_commit>) -> IO (CInt)+#ccall git_commit_create_v , Ptr <git_oid> -> Ptr <git_repository> -> CString -> Ptr <git_signature> -> Ptr <git_signature> -> CString -> CString -> Ptr <git_tree> -> CInt -> IO (CInt) #ccall git_commit_create_oid , Ptr <git_oid> -> Ptr <git_repository> -> CString -> Ptr <git_signature> -> Ptr <git_signature> -> CString -> CString -> Ptr <git_oid> -> CInt -> Ptr (Ptr <git_oid>) -> IO (CInt)
Bindings/Libgit2/Commit.hsc.helper.c view
@@ -1,6 +1,7 @@ #include <bindings.cmacros.h> #include <git2.h> -BC_INLINE3(git_commit_lookup, git_commit * *, git_repository *, const git_oid *, int)-BC_INLINE4(git_commit_lookup_prefix, git_commit * *, git_repository *, const git_oid *, unsigned, int)-BC_INLINE1VOID(git_commit_free, git_commit *)+BC_INLINE3(git_commit_lookup, git_commit**, git_repository*, const git_oid*, int)+BC_INLINE4(git_commit_lookup_prefix, git_commit**, git_repository*, const git_oid*, size_t, int)+BC_INLINE1VOID(git_commit_free, git_commit*)+BC_INLINE1(git_commit_id, const git_commit*, const git_oid*)
Bindings/Libgit2/Common.hsc view
@@ -1,20 +1,33 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Common where+import Foreign.Ptr #strict_import -{- typedef struct {- char * * strings; size_t count;- } git_strarray; -}-#starttype git_strarray-#field strings , Ptr (CString)-#field count , CSize-#stoptype-#ccall git_strarray_free , Ptr <git_strarray> -> IO ()-#ccall git_strarray_copy , Ptr <git_strarray> -> Ptr <git_strarray> -> IO (CInt) #ccall git_libgit2_version , Ptr CInt -> Ptr CInt -> Ptr CInt -> IO ()--{- BEGIN MANUAL ENTRY -}-#cinline GIT_PATH_LIST_SEPARATOR , CChar-#num GIT_PATH_MAX-{- END MANUAL ENTRY -}+{- enum {+ GIT_CAP_THREADS = 1 << 0, GIT_CAP_HTTPS = 1 << 1+}; -}+#num GIT_CAP_THREADS+#num GIT_CAP_HTTPS+#ccall git_libgit2_capabilities , IO (CInt)+{- enum {+ GIT_OPT_GET_MWINDOW_SIZE,+ GIT_OPT_SET_MWINDOW_SIZE,+ GIT_OPT_GET_MWINDOW_MAPPED_LIMIT,+ GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,+ GIT_OPT_GET_SEARCH_PATH,+ GIT_OPT_SET_SEARCH_PATH,+ GIT_OPT_GET_ODB_CACHE_SIZE,+ GIT_OPT_SET_ODB_CACHE_SIZE+}; -}+#num GIT_OPT_GET_MWINDOW_SIZE+#num GIT_OPT_SET_MWINDOW_SIZE+#num GIT_OPT_GET_MWINDOW_MAPPED_LIMIT+#num GIT_OPT_SET_MWINDOW_MAPPED_LIMIT+#num GIT_OPT_GET_SEARCH_PATH+#num GIT_OPT_SET_SEARCH_PATH+#num GIT_OPT_GET_ODB_CACHE_SIZE+#num GIT_OPT_SET_ODB_CACHE_SIZE+#ccall git_libgit2_opts , CInt -> IO (CInt)
Bindings/Libgit2/Config.hsc view
@@ -1,54 +1,83 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Config where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types-{- struct git_config_file {+{- enum {+ GIT_CONFIG_LEVEL_SYSTEM = 1,+ GIT_CONFIG_LEVEL_XDG = 2,+ GIT_CONFIG_LEVEL_GLOBAL = 3,+ GIT_CONFIG_LEVEL_LOCAL = 4,+ GIT_CONFIG_HIGHEST_LEVEL = -1+}; -}+#num GIT_CONFIG_LEVEL_SYSTEM+#num GIT_CONFIG_LEVEL_XDG+#num GIT_CONFIG_LEVEL_GLOBAL+#num GIT_CONFIG_LEVEL_LOCAL+#num GIT_CONFIG_HIGHEST_LEVEL+{- typedef struct {+ const char * name; const char * value; unsigned int level;+ } git_config_entry; -}+#starttype git_config_entry+#field name , CString+#field value , CString+#field level , CUInt+#stoptype+{- typedef int (* git_config_foreach_cb)(const git_config_entry *,+ void *); -}+#callback git_config_foreach_cb , Ptr (<git_config_entry>) -> Ptr () -> IO CInt+{- struct git_config_backend {+ unsigned int version; struct git_config * cfg;- int (* open)(struct git_config_file *);- int (* get)(struct git_config_file *,+ int (* open)(struct git_config_backend *, unsigned int level);+ int (* get)(const struct git_config_backend *, const char * key,- const char * * value);- int (* get_multivar)(struct git_config_file *,+ const git_config_entry * * entry);+ int (* get_multivar)(struct git_config_backend *, const char * key, const char * regexp,- int (* fn)(const char *, void *),- void * data);- int (* set)(struct git_config_file *,+ git_config_foreach_cb callback,+ void * payload);+ int (* set)(struct git_config_backend *, const char * key, const char * value);- int (* set_multivar)(git_config_file * cfg,+ int (* set_multivar)(git_config_backend * cfg, const char * name, const char * regexp, const char * value);- int (* del)(struct git_config_file *, const char * key);- int (* foreach)(struct git_config_file *,- int (* fn)(const char *, const char *, void *),- void * data);- void (* free)(struct git_config_file *);+ int (* del)(struct git_config_backend *, const char * key);+ int (* foreach)(struct git_config_backend *,+ const char *,+ git_config_foreach_cb callback,+ void * payload);+ int (* refresh)(struct git_config_backend *);+ void (* free)(struct git_config_backend *); }; -}-#callback git_config_open_callback , Ptr <git_config_file> -> IO CInt-#callback git_config_get_callback , Ptr <git_config_file> -> CString -> Ptr (CString) -> IO CInt-#callback git_config_get_multivar_inner_callback , CString -> Ptr () -> IO CInt-#callback git_config_get_multivar_callback , Ptr <git_config_file> -> CString -> CString -> <git_config_get_multivar_inner_callback> -> Ptr () -> IO CInt-#callback git_config_set_callback , Ptr <git_config_file> -> CString -> CString -> IO CInt-#callback git_config_set_multivar_callback , Ptr <git_config_file> -> CString -> CString -> CString -> IO CInt-#callback git_config_del_callback , Ptr <git_config_file> -> CString -> IO CInt-#callback git_config_foreach_inner_callback , CString -> CString -> Ptr () -> IO CInt-#callback git_config_foreach_callback , Ptr <git_config_file> -> <git_config_foreach_inner_callback> -> Ptr () -> IO CInt-#callback git_config_free_callback , Ptr <git_config_file> -> IO ()-#starttype git_config_file+#callback git_config_backend_open_callback , Ptr <git_config_backend> -> CUInt -> IO CInt+#callback git_config_backend_get_callback , Ptr <git_config_backend> -> CString -> Ptr (Ptr <git_config_entry>) -> IO CInt+#callback git_config_backend_get_multivar_callback , Ptr <git_config_backend> -> CString -> CString -> <git_config_foreach_cb> -> Ptr () -> IO CInt+#callback git_config_backend_set_callback , Ptr <git_config_backend> -> CString -> CString -> IO CInt+#callback git_config_backend_set_multivar_callback , Ptr <git_config_backend> -> CString -> CString -> CString -> IO CInt+#callback git_config_backend_del_callback , Ptr <git_config_backend> -> CString -> IO CInt+#callback git_config_backend_foreach_callback , Ptr <git_config_backend> -> CString -> <git_config_foreach_cb> -> Ptr () -> IO CInt+#callback git_config_backend_refresh_callback , Ptr <git_config_backend> -> IO CInt+#callback git_config_backend_free_callback , Ptr <git_config_backend> -> IO ()+#starttype git_config_backend+#field version , CUInt #field cfg , Ptr <git_config>-#field open , <git_config_open_callback>-#field get , <git_config_get_callback>-#field get_multivar , <git_config_get_multivar_callback>-#field set , <git_config_set_callback>-#field set_multivar , <git_config_set_multivar_callback>-#field del , <git_config_del_callback>-#field foreach , <git_config_foreach_callback>-#field free , <git_config_free_callback>+#field open , <git_config_backend_open_callback>+#field get , <git_config_backend_get_callback>+#field get_multivar , <git_config_backend_get_multivar_callback>+#field set , <git_config_backend_set_callback>+#field set_multivar , <git_config_backend_set_multivar_callback>+#field del , <git_config_backend_del_callback>+#field foreach , <git_config_backend_foreach_callback>+#field refresh , <git_config_backend_refresh_callback>+#field free , <git_config_backend_free_callback> #stoptype {- typedef enum { GIT_CVAR_FALSE = 0,@@ -70,24 +99,32 @@ #field map_value , CInt #stoptype #ccall git_config_find_global , CString -> CSize -> IO (CInt)+#ccall git_config_find_xdg , CString -> CSize -> IO (CInt) #ccall git_config_find_system , CString -> CSize -> IO (CInt)-#ccall git_config_open_global , Ptr (Ptr <git_config>) -> IO (CInt)-#ccall git_config_file__ondisk , Ptr (Ptr <git_config_file>) -> CString -> IO (CInt)+#ccall git_config_open_default , Ptr (Ptr <git_config>) -> IO (CInt) #ccall git_config_new , Ptr (Ptr <git_config>) -> IO (CInt)-#ccall git_config_add_file , Ptr <git_config> -> Ptr <git_config_file> -> CInt -> IO (CInt)-#ccall git_config_add_file_ondisk , Ptr <git_config> -> CString -> CInt -> IO (CInt)+#ccall git_config_add_backend , Ptr <git_config> -> Ptr <git_config_backend> -> CUInt -> CInt -> IO (CInt)+#ccall git_config_add_file_ondisk , Ptr <git_config> -> CString -> CUInt -> CInt -> IO (CInt) #ccall git_config_open_ondisk , Ptr (Ptr <git_config>) -> CString -> IO (CInt)+#ccall git_config_open_level , Ptr (Ptr <git_config>) -> Ptr <git_config> -> CUInt -> IO (CInt)+#ccall git_config_refresh , Ptr <git_config> -> IO (CInt) #ccall git_config_free , Ptr <git_config> -> IO ()+#ccall git_config_get_entry , Ptr (Ptr <git_config_entry>) -> Ptr <git_config> -> CString -> IO (CInt) #ccall git_config_get_int32 , Ptr CInt -> Ptr <git_config> -> CString -> IO (CInt) #ccall git_config_get_int64 , Ptr CLong -> Ptr <git_config> -> CString -> IO (CInt) #ccall git_config_get_bool , Ptr CInt -> Ptr <git_config> -> CString -> IO (CInt) #ccall git_config_get_string , Ptr (CString) -> Ptr <git_config> -> CString -> IO (CInt)-#ccall git_config_get_multivar , Ptr <git_config> -> CString -> CString -> <git_config_get_multivar_inner_callback> -> Ptr () -> IO (CInt)+#ccall git_config_get_multivar , Ptr <git_config> -> CString -> CString -> <git_config_foreach_cb> -> Ptr () -> IO (CInt) #ccall git_config_set_int32 , Ptr <git_config> -> CString -> CInt -> IO (CInt) #ccall git_config_set_int64 , Ptr <git_config> -> CString -> CLong -> IO (CInt) #ccall git_config_set_bool , Ptr <git_config> -> CString -> CInt -> IO (CInt) #ccall git_config_set_string , Ptr <git_config> -> CString -> CString -> IO (CInt) #ccall git_config_set_multivar , Ptr <git_config> -> CString -> CString -> CString -> IO (CInt)-#ccall git_config_delete , Ptr <git_config> -> CString -> IO (CInt)-#ccall git_config_foreach , Ptr <git_config> -> <git_config_foreach_inner_callback> -> Ptr () -> IO (CInt)+#ccall git_config_delete_entry , Ptr <git_config> -> CString -> IO (CInt)+#ccall git_config_foreach , Ptr <git_config> -> <git_config_foreach_cb> -> Ptr () -> IO (CInt)+#ccall git_config_foreach_match , Ptr <git_config> -> CString -> <git_config_foreach_cb> -> Ptr () -> IO (CInt) #ccall git_config_get_mapped , Ptr CInt -> Ptr <git_config> -> CString -> Ptr <git_cvar_map> -> CSize -> IO (CInt)+#ccall git_config_lookup_map_value , Ptr CInt -> Ptr <git_cvar_map> -> CSize -> CString -> IO (CInt)+#ccall git_config_parse_bool , Ptr CInt -> CString -> IO (CInt)+#ccall git_config_parse_int32 , Ptr CInt -> CString -> IO (CInt)+#ccall git_config_parse_int64 , Ptr CLong -> CString -> IO (CInt)
+ Bindings/Libgit2/CredHelpers.hsc view
@@ -0,0 +1,17 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/cred_helpers.h>+module Bindings.Libgit2.CredHelpers where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Transport+{- typedef struct git_cred_userpass_payload {+ char * username; char * password;+ } git_cred_userpass_payload; -}+#starttype git_cred_userpass_payload+#field username , CString+#field password , CString+#stoptype+#ccall git_cred_userpass , Ptr (Ptr <git_cred>) -> CString -> CString -> CUInt -> Ptr () -> IO (CInt)
Bindings/Libgit2/Diff.hsc view
@@ -1,27 +1,39 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Diff where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types+import Bindings.Libgit2.Strarray import Bindings.Libgit2.Oid import Bindings.Libgit2.Tree import Bindings.Libgit2.Refs-{- enum {- GIT_DIFF_NORMAL = 0,- GIT_DIFF_REVERSE = 1 << 0,- GIT_DIFF_FORCE_TEXT = 1 << 1,- GIT_DIFF_IGNORE_WHITESPACE = 1 << 2,- GIT_DIFF_IGNORE_WHITESPACE_CHANGE = 1 << 3,- GIT_DIFF_IGNORE_WHITESPACE_EOL = 1 << 4,- GIT_DIFF_IGNORE_SUBMODULES = 1 << 5,- GIT_DIFF_PATIENCE = 1 << 6,- GIT_DIFF_INCLUDE_IGNORED = 1 << 7,- GIT_DIFF_INCLUDE_UNTRACKED = 1 << 8,- GIT_DIFF_INCLUDE_UNMODIFIED = 1 << 9,- GIT_DIFF_RECURSE_UNTRACKED_DIRS = 1 << 10-}; -}+{- typedef enum {+ GIT_DIFF_NORMAL = 0,+ GIT_DIFF_REVERSE = 1 << 0,+ GIT_DIFF_FORCE_TEXT = 1 << 1,+ GIT_DIFF_IGNORE_WHITESPACE = 1 << 2,+ GIT_DIFF_IGNORE_WHITESPACE_CHANGE = 1 << 3,+ GIT_DIFF_IGNORE_WHITESPACE_EOL = 1 << 4,+ GIT_DIFF_IGNORE_SUBMODULES = 1 << 5,+ GIT_DIFF_PATIENCE = 1 << 6,+ GIT_DIFF_INCLUDE_IGNORED = 1 << 7,+ GIT_DIFF_INCLUDE_UNTRACKED = 1 << 8,+ GIT_DIFF_INCLUDE_UNMODIFIED = 1 << 9,+ GIT_DIFF_RECURSE_UNTRACKED_DIRS = 1 << 10,+ GIT_DIFF_DISABLE_PATHSPEC_MATCH = 1 << 11,+ GIT_DIFF_DELTAS_ARE_ICASE = 1 << 12,+ GIT_DIFF_INCLUDE_UNTRACKED_CONTENT = 1 << 13,+ GIT_DIFF_SKIP_BINARY_CHECK = 1 << 14,+ GIT_DIFF_INCLUDE_TYPECHANGE = 1 << 15,+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES = 1 << 16,+ GIT_DIFF_IGNORE_FILEMODE = 1 << 17,+ GIT_DIFF_RECURSE_IGNORED_DIRS = 1 << 18+ } git_diff_option_t; -}+#integral_t git_diff_option_t #num GIT_DIFF_NORMAL #num GIT_DIFF_REVERSE #num GIT_DIFF_FORCE_TEXT@@ -34,39 +46,26 @@ #num GIT_DIFF_INCLUDE_UNTRACKED #num GIT_DIFF_INCLUDE_UNMODIFIED #num GIT_DIFF_RECURSE_UNTRACKED_DIRS-{- typedef struct {- uint32_t flags;- uint16_t context_lines;- uint16_t interhunk_lines;- char * old_prefix;- char * new_prefix;- git_strarray pathspec;- } git_diff_options; -}-#starttype git_diff_options-#field flags , CUInt-#field context_lines , CUShort-#field interhunk_lines , CUShort-#field old_prefix , CString-#field new_prefix , CString-#field pathspec , <git_strarray>-#stoptype+#num GIT_DIFF_DISABLE_PATHSPEC_MATCH+#num GIT_DIFF_DELTAS_ARE_ICASE+#num GIT_DIFF_INCLUDE_UNTRACKED_CONTENT+#num GIT_DIFF_SKIP_BINARY_CHECK+#num GIT_DIFF_INCLUDE_TYPECHANGE+#num GIT_DIFF_INCLUDE_TYPECHANGE_TREES+#num GIT_DIFF_IGNORE_FILEMODE+#num GIT_DIFF_RECURSE_IGNORED_DIRS {- typedef struct git_diff_list git_diff_list; -} #opaque_t git_diff_list-{- enum {- GIT_DIFF_FILE_VALID_OID = 1 << 0,- GIT_DIFF_FILE_FREE_PATH = 1 << 1,- GIT_DIFF_FILE_BINARY = 1 << 2,- GIT_DIFF_FILE_NOT_BINARY = 1 << 3,- GIT_DIFF_FILE_FREE_DATA = 1 << 4,- GIT_DIFF_FILE_UNMAP_DATA = 1 << 5-}; -}-#num GIT_DIFF_FILE_VALID_OID-#num GIT_DIFF_FILE_FREE_PATH-#num GIT_DIFF_FILE_BINARY-#num GIT_DIFF_FILE_NOT_BINARY-#num GIT_DIFF_FILE_FREE_DATA-#num GIT_DIFF_FILE_UNMAP_DATA {- typedef enum {+ GIT_DIFF_FLAG_BINARY = 1 << 0,+ GIT_DIFF_FLAG_NOT_BINARY = 1 << 1,+ GIT_DIFF_FLAG_VALID_OID = 1 << 2+ } git_diff_flag_t; -}+#integral_t git_diff_flag_t+#num GIT_DIFF_FLAG_BINARY+#num GIT_DIFF_FLAG_NOT_BINARY+#num GIT_DIFF_FLAG_VALID_OID+{- typedef enum { GIT_DELTA_UNMODIFIED = 0, GIT_DELTA_ADDED = 1, GIT_DELTA_DELETED = 2,@@ -74,7 +73,8 @@ GIT_DELTA_RENAMED = 4, GIT_DELTA_COPIED = 5, GIT_DELTA_IGNORED = 6,- GIT_DELTA_UNTRACKED = 7+ GIT_DELTA_UNTRACKED = 7,+ GIT_DELTA_TYPECHANGE = 8 } git_delta_t; -} #integral_t git_delta_t #num GIT_DELTA_UNMODIFIED@@ -85,39 +85,69 @@ #num GIT_DELTA_COPIED #num GIT_DELTA_IGNORED #num GIT_DELTA_UNTRACKED+#num GIT_DELTA_TYPECHANGE {- typedef struct { git_oid oid;- char * path;- uint16_t mode;+ const char * path; git_off_t size;- unsigned int flags;+ uint32_t flags;+ uint16_t mode; } git_diff_file; -} #starttype git_diff_file #field oid , <git_oid> #field path , CString-#field mode , CUShort #field size , CLong #field flags , CUInt+#field mode , CUShort #stoptype {- typedef struct { git_diff_file old_file; git_diff_file new_file; git_delta_t status;- unsigned int similarity;- int binary;+ uint32_t similarity;+ uint32_t flags; } git_diff_delta; -} #starttype git_diff_delta #field old_file , <git_diff_file> #field new_file , <git_diff_file> #field status , <git_delta_t> #field similarity , CUInt-#field binary , CInt+#field flags , CUInt #stoptype-{- typedef int (* git_diff_file_fn)(void * cb_data,- git_diff_delta * delta,- float progress); -}-#synonym_t git_diff_file_fn , CInt+{- typedef int (* git_diff_notify_cb)(const git_diff_list * diff_so_far,+ const git_diff_delta * delta_to_add,+ const char * matched_pathspec,+ void * payload); -}+#callback git_diff_notify_cb , Ptr (<git_diff_list>) -> Ptr (<git_diff_delta>) -> CString -> Ptr () -> IO CInt {- typedef struct {+ unsigned int version;+ uint32_t flags;+ uint16_t context_lines;+ uint16_t interhunk_lines;+ const char * old_prefix;+ const char * new_prefix;+ git_strarray pathspec;+ git_off_t max_size;+ git_diff_notify_cb notify_cb;+ void * notify_payload;+ } git_diff_options; -}+#starttype git_diff_options+#field version , CUInt+#field flags , CUInt+#field context_lines , CUShort+#field interhunk_lines , CUShort+#field old_prefix , CString+#field new_prefix , CString+#field pathspec , <git_strarray>+#field max_size , CLong+#field notify_cb , <git_diff_notify_cb>+#field notify_payload , Ptr ()+#stoptype+{- typedef int (* git_diff_file_cb)(const git_diff_delta * delta,+ float progress,+ void * payload); -}+#callback git_diff_file_cb , Ptr (<git_diff_delta>) -> CFloat -> Ptr () -> IO CInt+{- typedef struct { int old_start; int old_lines; int new_start; int new_lines; } git_diff_range; -} #starttype git_diff_range@@ -126,22 +156,23 @@ #field new_start , CInt #field new_lines , CInt #stoptype-{- typedef int (* git_diff_hunk_fn)(void * cb_data,- git_diff_delta * delta,- git_diff_range * range,+{- typedef int (* git_diff_hunk_cb)(const git_diff_delta * delta,+ const git_diff_range * range, const char * header,- size_t header_len); -}-#synonym_t git_diff_hunk_fn , CInt-{- enum {- GIT_DIFF_LINE_CONTEXT = ' ',- GIT_DIFF_LINE_ADDITION = '+',- GIT_DIFF_LINE_DELETION = '-',- GIT_DIFF_LINE_ADD_EOFNL = '\n',- GIT_DIFF_LINE_DEL_EOFNL = '\0',- GIT_DIFF_LINE_FILE_HDR = 'F',- GIT_DIFF_LINE_HUNK_HDR = 'H',- GIT_DIFF_LINE_BINARY = 'B'-}; -}+ size_t header_len,+ void * payload); -}+#callback git_diff_hunk_cb , Ptr (<git_diff_delta>) -> Ptr (<git_diff_range>) -> CString -> CSize -> Ptr () -> IO CInt+{- typedef enum {+ GIT_DIFF_LINE_CONTEXT = ' ',+ GIT_DIFF_LINE_ADDITION = '+',+ GIT_DIFF_LINE_DELETION = '-',+ GIT_DIFF_LINE_ADD_EOFNL = '\n',+ GIT_DIFF_LINE_DEL_EOFNL = '\0',+ GIT_DIFF_LINE_FILE_HDR = 'F',+ GIT_DIFF_LINE_HUNK_HDR = 'H',+ GIT_DIFF_LINE_BINARY = 'B'+ } git_diff_line_t; -}+#integral_t git_diff_line_t #num GIT_DIFF_LINE_CONTEXT #num GIT_DIFF_LINE_ADDITION #num GIT_DIFF_LINE_DELETION@@ -150,20 +181,106 @@ #num GIT_DIFF_LINE_FILE_HDR #num GIT_DIFF_LINE_HUNK_HDR #num GIT_DIFF_LINE_BINARY-{- typedef int (* git_diff_data_fn)(void * cb_data,- git_diff_delta * delta,- git_diff_range * range,+{- typedef int (* git_diff_data_cb)(const git_diff_delta * delta,+ const git_diff_range * range, char line_origin, const char * content,- size_t content_len); -}-#synonym_t git_diff_data_fn , CInt+ size_t content_len,+ void * payload); -}+#callback git_diff_data_cb , Ptr (<git_diff_delta>) -> Ptr (<git_diff_range>) -> CChar -> CString -> CSize -> Ptr () -> IO CInt+{- typedef struct git_diff_patch git_diff_patch; -}+#opaque_t git_diff_patch+{- typedef enum {+ GIT_DIFF_FIND_RENAMES = 1 << 0,+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES = 1 << 1,+ GIT_DIFF_FIND_COPIES = 1 << 2,+ GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = 1 << 3,+ GIT_DIFF_FIND_AND_BREAK_REWRITES = 1 << 4,+ GIT_DIFF_FIND_ALL = 0x1f,+ GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0,+ GIT_DIFF_FIND_IGNORE_WHITESPACE = 1 << 6,+ GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = 1 << 7+ } git_diff_find_t; -}+#integral_t git_diff_find_t+#num GIT_DIFF_FIND_RENAMES+#num GIT_DIFF_FIND_RENAMES_FROM_REWRITES+#num GIT_DIFF_FIND_COPIES+#num GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED+#num GIT_DIFF_FIND_AND_BREAK_REWRITES+#num GIT_DIFF_FIND_ALL+#num GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE+#num GIT_DIFF_FIND_IGNORE_WHITESPACE+#num GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE+{- typedef struct {+ int (* file_signature)(void * * out,+ const git_diff_file * file,+ const char * fullpath,+ void * payload);+ int (* buffer_signature)(void * * out,+ const git_diff_file * file,+ const char * buf,+ size_t buflen,+ void * payload);+ void (* free_signature)(void * sig, void * payload);+ int (* similarity)(int * score,+ void * siga,+ void * sigb,+ void * payload);+ void * payload;+ } git_diff_similarity_metric; -}+#callback git_diff_similarity_metric_file_signature_callback , Ptr (Ptr ()) -> Ptr <git_diff_file> -> CString -> Ptr () -> IO CInt+#callback git_diff_similarity_metric_buffer_signature_callback , Ptr (Ptr ()) -> Ptr <git_diff_file> -> CString -> CSize -> Ptr () -> IO CInt+#callback git_diff_similarity_metric_free_signature_callback , Ptr () -> Ptr () -> IO ()+#callback git_diff_similarity_metric_similarity_callback , Ptr CInt -> Ptr () -> Ptr () -> Ptr () -> IO CInt+#starttype git_diff_similarity_metric+#field file_signature , <git_diff_similarity_metric_file_signature_callback>+#field buffer_signature , <git_diff_similarity_metric_buffer_signature_callback>+#field free_signature , <git_diff_similarity_metric_free_signature_callback>+#field similarity , <git_diff_similarity_metric_similarity_callback>+#field payload , Ptr ()+#stoptype+{- typedef struct {+ unsigned int version;+ unsigned int flags;+ unsigned int rename_threshold;+ unsigned int rename_from_rewrite_threshold;+ unsigned int copy_threshold;+ unsigned int break_rewrite_threshold;+ unsigned int target_limit;+ git_diff_similarity_metric * metric;+ } git_diff_find_options; -}+#starttype git_diff_find_options+#field version , CUInt+#field flags , CUInt+#field rename_threshold , CUInt+#field rename_from_rewrite_threshold , CUInt+#field copy_threshold , CUInt+#field break_rewrite_threshold , CUInt+#field target_limit , CUInt+#field metric , Ptr <git_diff_similarity_metric>+#stoptype #ccall git_diff_list_free , Ptr <git_diff_list> -> IO ()-#ccall git_diff_tree_to_tree , Ptr <git_repository> -> Ptr <git_diff_options> -> Ptr <git_tree> -> Ptr <git_tree> -> Ptr (Ptr <git_diff_list>) -> IO (CInt)-#ccall git_diff_index_to_tree , Ptr <git_repository> -> Ptr <git_diff_options> -> Ptr <git_tree> -> Ptr (Ptr <git_diff_list>) -> IO (CInt)-#ccall git_diff_workdir_to_index , Ptr <git_repository> -> Ptr <git_diff_options> -> Ptr (Ptr <git_diff_list>) -> IO (CInt)-#ccall git_diff_workdir_to_tree , Ptr <git_repository> -> Ptr <git_diff_options> -> Ptr <git_tree> -> Ptr (Ptr <git_diff_list>) -> IO (CInt)+#ccall git_diff_tree_to_tree , Ptr (Ptr <git_diff_list>) -> Ptr <git_repository> -> Ptr <git_tree> -> Ptr <git_tree> -> Ptr <git_diff_options> -> IO (CInt)+#ccall git_diff_tree_to_index , Ptr (Ptr <git_diff_list>) -> Ptr <git_repository> -> Ptr <git_tree> -> Ptr <git_index> -> Ptr <git_diff_options> -> IO (CInt)+#ccall git_diff_index_to_workdir , Ptr (Ptr <git_diff_list>) -> Ptr <git_repository> -> Ptr <git_index> -> Ptr <git_diff_options> -> IO (CInt)+#ccall git_diff_tree_to_workdir , Ptr (Ptr <git_diff_list>) -> Ptr <git_repository> -> Ptr <git_tree> -> Ptr <git_diff_options> -> IO (CInt) #ccall git_diff_merge , Ptr <git_diff_list> -> Ptr <git_diff_list> -> IO (CInt)-#ccall git_diff_foreach , Ptr <git_diff_list> -> Ptr () -> CInt -> CInt -> CInt -> IO (CInt)-#ccall git_diff_print_compact , Ptr <git_diff_list> -> Ptr () -> CInt -> IO (CInt)-#ccall git_diff_print_patch , Ptr <git_diff_list> -> Ptr () -> CInt -> IO (CInt)-#ccall git_diff_blobs , Ptr <git_blob> -> Ptr <git_blob> -> Ptr <git_diff_options> -> Ptr () -> CInt -> CInt -> CInt -> IO (CInt)+#ccall git_diff_find_similar , Ptr <git_diff_list> -> Ptr <git_diff_find_options> -> IO (CInt)+#ccall git_diff_foreach , Ptr <git_diff_list> -> <git_diff_file_cb> -> <git_diff_hunk_cb> -> <git_diff_data_cb> -> Ptr () -> IO (CInt)+#ccall git_diff_print_compact , Ptr <git_diff_list> -> <git_diff_data_cb> -> Ptr () -> IO (CInt)+#ccall git_diff_status_char , <git_delta_t> -> IO (CChar)+#ccall git_diff_print_patch , Ptr <git_diff_list> -> <git_diff_data_cb> -> Ptr () -> IO (CInt)+#ccall git_diff_num_deltas , Ptr <git_diff_list> -> IO (CSize)+#ccall git_diff_num_deltas_of_type , Ptr <git_diff_list> -> <git_delta_t> -> IO (CSize)+#ccall git_diff_get_patch , Ptr (Ptr <git_diff_patch>) -> Ptr (Ptr <git_diff_delta>) -> Ptr <git_diff_list> -> CSize -> IO (CInt)+#ccall git_diff_patch_free , Ptr <git_diff_patch> -> IO ()+#ccall git_diff_patch_delta , Ptr <git_diff_patch> -> IO (Ptr <git_diff_delta>)+#ccall git_diff_patch_num_hunks , Ptr <git_diff_patch> -> IO (CSize)+#ccall git_diff_patch_line_stats , Ptr CSize -> Ptr CSize -> Ptr CSize -> Ptr <git_diff_patch> -> IO (CInt)+#ccall git_diff_patch_get_hunk , Ptr (Ptr <git_diff_range>) -> Ptr (CString) -> Ptr CSize -> Ptr CSize -> Ptr <git_diff_patch> -> CSize -> IO (CInt)+#ccall git_diff_patch_num_lines_in_hunk , Ptr <git_diff_patch> -> CSize -> IO (CInt)+#ccall git_diff_patch_get_line_in_hunk , CString -> Ptr (CString) -> Ptr CSize -> Ptr CInt -> Ptr CInt -> Ptr <git_diff_patch> -> CSize -> CSize -> IO (CInt)+#ccall git_diff_patch_print , Ptr <git_diff_patch> -> <git_diff_data_cb> -> Ptr () -> IO (CInt)+#ccall git_diff_patch_to_str , Ptr (CString) -> Ptr <git_diff_patch> -> IO (CInt)+#ccall git_diff_blobs , Ptr <git_blob> -> Ptr <git_blob> -> Ptr <git_diff_options> -> <git_diff_file_cb> -> <git_diff_hunk_cb> -> <git_diff_data_cb> -> Ptr () -> IO (CInt)+#ccall git_diff_blob_to_buffer , Ptr <git_blob> -> CString -> CSize -> Ptr <git_diff_options> -> <git_diff_file_cb> -> <git_diff_hunk_cb> -> <git_diff_data_cb> -> Ptr () -> IO (CInt)
Bindings/Libgit2/Errors.hsc view
@@ -1,6 +1,8 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Errors where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common@@ -11,8 +13,15 @@ GIT_EEXISTS = -4, GIT_EAMBIGUOUS = -5, GIT_EBUFS = -6,+ GIT_EUSER = -7,+ GIT_EBAREREPO = -8,+ GIT_EORPHANEDHEAD = -9,+ GIT_EUNMERGED = -10,+ GIT_ENONFASTFORWARD = -11,+ GIT_EINVALIDSPEC = -12,+ GIT_EMERGECONFLICT = -13, GIT_PASSTHROUGH = -30,- GIT_REVWALKOVER = -31+ GIT_ITEROVER = -31 }; -} #num GIT_OK #num GIT_ERROR@@ -20,8 +29,15 @@ #num GIT_EEXISTS #num GIT_EAMBIGUOUS #num GIT_EBUFS+#num GIT_EUSER+#num GIT_EBAREREPO+#num GIT_EORPHANEDHEAD+#num GIT_EUNMERGED+#num GIT_ENONFASTFORWARD+#num GIT_EINVALIDSPEC+#num GIT_EMERGECONFLICT #num GIT_PASSTHROUGH-#num GIT_REVWALKOVER+#num GIT_ITEROVER {- typedef struct { char * message; int klass; } git_error; -}@@ -44,7 +60,14 @@ GITERR_NET, GITERR_TAG, GITERR_TREE,- GITERR_INDEXER+ GITERR_INDEXER,+ GITERR_SSL,+ GITERR_SUBMODULE,+ GITERR_THREAD,+ GITERR_STASH,+ GITERR_CHECKOUT,+ GITERR_FETCHHEAD,+ GITERR_MERGE } git_error_t; -} #integral_t git_error_t #num GITERR_NOMEMORY@@ -62,5 +85,14 @@ #num GITERR_TAG #num GITERR_TREE #num GITERR_INDEXER+#num GITERR_SSL+#num GITERR_SUBMODULE+#num GITERR_THREAD+#num GITERR_STASH+#num GITERR_CHECKOUT+#num GITERR_FETCHHEAD+#num GITERR_MERGE #ccall giterr_last , IO (Ptr <git_error>) #ccall giterr_clear , IO ()+#ccall giterr_set_str , CInt -> CString -> IO ()+#ccall giterr_set_oom , IO ()
+ Bindings/Libgit2/Graph.hsc view
@@ -0,0 +1,12 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/graph.h>+module Bindings.Libgit2.Graph where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+import Bindings.Libgit2.Oid+#ccall git_graph_ahead_behind , Ptr CSize -> Ptr CSize -> Ptr <git_repository> -> Ptr <git_oid> -> Ptr <git_oid> -> IO (CInt)
+ Bindings/Libgit2/Ignore.hsc view
@@ -0,0 +1,13 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/ignore.h>+module Bindings.Libgit2.Ignore where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+#ccall git_ignore_add_rule , Ptr <git_repository> -> CString -> IO (CInt)+#ccall git_ignore_clear_internal_rules , Ptr <git_repository> -> IO (CInt)+#ccall git_ignore_path_is_ignored , Ptr CInt -> Ptr <git_repository> -> CString -> IO (CInt)
Bindings/Libgit2/Index.hsc view
@@ -1,9 +1,12 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Index where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common+import Bindings.Libgit2.Indexer import Bindings.Libgit2.Types import Bindings.Libgit2.Oid {- typedef struct {@@ -41,30 +44,55 @@ #field flags_extended , CUShort #field path , CString #stoptype-{- typedef struct git_index_entry_unmerged {+{- typedef struct git_index_reuc_entry { unsigned int mode[3]; git_oid oid[3]; char * path;- } git_index_entry_unmerged; -}-#starttype git_index_entry_unmerged+ } git_index_reuc_entry; -}+#starttype git_index_reuc_entry #array_field mode , CUInt #array_field oid , <git_oid> #field path , CString #stoptype+{- enum {+ GIT_INDEXCAP_IGNORE_CASE = 1,+ GIT_INDEXCAP_NO_FILEMODE = 2,+ GIT_INDEXCAP_NO_SYMLINKS = 4,+ GIT_INDEXCAP_FROM_OWNER = ~0u+}; -}+#num GIT_INDEXCAP_IGNORE_CASE+#num GIT_INDEXCAP_NO_FILEMODE+#num GIT_INDEXCAP_NO_SYMLINKS+#num GIT_INDEXCAP_FROM_OWNER #ccall git_index_open , Ptr (Ptr <git_index>) -> CString -> IO (CInt)-#ccall git_index_clear , Ptr <git_index> -> IO ()+#ccall git_index_new , Ptr (Ptr <git_index>) -> IO (CInt) #ccall git_index_free , Ptr <git_index> -> IO ()+#ccall git_index_owner , Ptr <git_index> -> IO (Ptr <git_repository>)+#ccall git_index_caps , Ptr <git_index> -> IO (CUInt)+#ccall git_index_set_caps , Ptr <git_index> -> CUInt -> IO (CInt) #ccall git_index_read , Ptr <git_index> -> IO (CInt) #ccall git_index_write , Ptr <git_index> -> IO (CInt)-#ccall git_index_find , Ptr <git_index> -> CString -> IO (CInt)-#ccall git_index_uniq , Ptr <git_index> -> IO ()-#ccall git_index_add , Ptr <git_index> -> CString -> CInt -> IO (CInt)-#ccall git_index_add2 , Ptr <git_index> -> Ptr <git_index_entry> -> IO (CInt)-#ccall git_index_append , Ptr <git_index> -> CString -> CInt -> IO (CInt)-#ccall git_index_append2 , Ptr <git_index> -> Ptr <git_index_entry> -> IO (CInt)-#ccall git_index_remove , Ptr <git_index> -> CInt -> IO (CInt)-#ccall git_index_get , Ptr <git_index> -> CUInt -> IO (Ptr <git_index_entry>)-#ccall git_index_entrycount , Ptr <git_index> -> IO (CUInt)-#ccall git_index_entrycount_unmerged , Ptr <git_index> -> IO (CUInt)-#ccall git_index_get_unmerged_bypath , Ptr <git_index> -> CString -> IO (Ptr <git_index_entry_unmerged>)-#ccall git_index_get_unmerged_byindex , Ptr <git_index> -> CUInt -> IO (Ptr <git_index_entry_unmerged>)-#ccall git_index_entry_stage , Ptr <git_index_entry> -> IO (CInt) #ccall git_index_read_tree , Ptr <git_index> -> Ptr <git_tree> -> IO (CInt)+#ccall git_index_write_tree , Ptr <git_oid> -> Ptr <git_index> -> IO (CInt)+#ccall git_index_write_tree_to , Ptr <git_oid> -> Ptr <git_index> -> Ptr <git_repository> -> IO (CInt)+#ccall git_index_entrycount , Ptr <git_index> -> IO (CSize)+#ccall git_index_clear , Ptr <git_index> -> IO ()+#ccall git_index_get_byindex , Ptr <git_index> -> CSize -> IO (Ptr <git_index_entry>)+#ccall git_index_get_bypath , Ptr <git_index> -> CString -> CInt -> IO (Ptr <git_index_entry>)+#ccall git_index_remove , Ptr <git_index> -> CString -> CInt -> IO (CInt)+#ccall git_index_remove_directory , Ptr <git_index> -> CString -> CInt -> IO (CInt)+#ccall git_index_add , Ptr <git_index> -> Ptr <git_index_entry> -> IO (CInt)+#ccall git_index_entry_stage , Ptr <git_index_entry> -> IO (CInt)+#ccall git_index_add_bypath , Ptr <git_index> -> CString -> IO (CInt)+#ccall git_index_remove_bypath , Ptr <git_index> -> CString -> IO (CInt)+#ccall git_index_find , Ptr CSize -> Ptr <git_index> -> CString -> IO (CInt)+#ccall git_index_conflict_add , Ptr <git_index> -> Ptr <git_index_entry> -> Ptr <git_index_entry> -> Ptr <git_index_entry> -> IO (CInt)+#ccall git_index_conflict_get , Ptr (Ptr <git_index_entry>) -> Ptr (Ptr <git_index_entry>) -> Ptr (Ptr <git_index_entry>) -> Ptr <git_index> -> CString -> IO (CInt)+#ccall git_index_conflict_remove , Ptr <git_index> -> CString -> IO (CInt)+#ccall git_index_conflict_cleanup , Ptr <git_index> -> IO ()+#ccall git_index_has_conflicts , Ptr <git_index> -> IO (CInt)+#ccall git_index_reuc_entrycount , Ptr <git_index> -> IO (CUInt)+#ccall git_index_reuc_find , Ptr CSize -> Ptr <git_index> -> CString -> IO (CInt)+#ccall git_index_reuc_get_bypath , Ptr <git_index> -> CString -> IO (Ptr <git_index_reuc_entry>)+#ccall git_index_reuc_get_byindex , Ptr <git_index> -> CSize -> IO (Ptr <git_index_reuc_entry>)+#ccall git_index_reuc_add , Ptr <git_index> -> CString -> CInt -> Ptr <git_oid> -> CInt -> Ptr <git_oid> -> CInt -> Ptr <git_oid> -> IO (CInt)+#ccall git_index_reuc_remove , Ptr <git_index> -> CSize -> IO (CInt)+#ccall git_index_reuc_clear , Ptr <git_index> -> IO ()
Bindings/Libgit2/Indexer.hsc view
@@ -1,28 +1,31 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Indexer where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Oid-{- typedef struct git_indexer_stats {- unsigned int total; unsigned int processed;- } git_indexer_stats; -}-#starttype git_indexer_stats-#field total , CUInt-#field processed , CUInt+{- typedef struct git_transfer_progress {+ unsigned int total_objects;+ unsigned int indexed_objects;+ unsigned int received_objects;+ size_t received_bytes;+ } git_transfer_progress; -}+#starttype git_transfer_progress+#field total_objects , CUInt+#field indexed_objects , CUInt+#field received_objects , CUInt+#field received_bytes , CSize #stoptype-{- typedef struct git_indexer git_indexer; -}-#opaque_t git_indexer+{- typedef int (* git_transfer_progress_callback)(const git_transfer_progress * stats,+ void * payload); -}+#callback git_transfer_progress_callback , Ptr (<git_transfer_progress>) -> Ptr () -> IO CInt {- typedef struct git_indexer_stream git_indexer_stream; -} #opaque_t git_indexer_stream-#ccall git_indexer_stream_new , Ptr (Ptr <git_indexer_stream>) -> CString -> IO (CInt)-#ccall git_indexer_stream_add , Ptr <git_indexer_stream> -> Ptr () -> CSize -> Ptr <git_indexer_stats> -> IO (CInt)-#ccall git_indexer_stream_finalize , Ptr <git_indexer_stream> -> Ptr <git_indexer_stats> -> IO (CInt)+#ccall git_indexer_stream_new , Ptr (Ptr <git_indexer_stream>) -> CString -> <git_transfer_progress_callback> -> Ptr () -> IO (CInt)+#ccall git_indexer_stream_add , Ptr <git_indexer_stream> -> Ptr () -> CSize -> Ptr <git_transfer_progress> -> IO (CInt)+#ccall git_indexer_stream_finalize , Ptr <git_indexer_stream> -> Ptr <git_transfer_progress> -> IO (CInt) #ccall git_indexer_stream_hash , Ptr <git_indexer_stream> -> IO (Ptr <git_oid>) #ccall git_indexer_stream_free , Ptr <git_indexer_stream> -> IO ()-#ccall git_indexer_new , Ptr (Ptr <git_indexer>) -> CString -> IO (CInt)-#ccall git_indexer_run , Ptr <git_indexer> -> Ptr <git_indexer_stats> -> IO (CInt)-#ccall git_indexer_write , Ptr <git_indexer> -> IO (CInt)-#ccall git_indexer_hash , Ptr <git_indexer> -> IO (Ptr <git_oid>)-#ccall git_indexer_free , Ptr <git_indexer> -> IO ()
Bindings/Libgit2/Merge.hsc view
@@ -1,9 +1,12 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Merge where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid #ccall git_merge_base , Ptr <git_oid> -> Ptr <git_repository> -> Ptr <git_oid> -> Ptr <git_oid> -> IO (CInt)+#ccall git_merge_base_many , Ptr <git_oid> -> Ptr <git_repository> -> Ptr (<git_oid>) -> CSize -> IO (CInt)
+ Bindings/Libgit2/Message.hsc view
@@ -0,0 +1,10 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/message.h>+module Bindings.Libgit2.Message where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+#ccall git_message_prettify , CString -> CSize -> CString -> CInt -> IO (CInt)
Bindings/Libgit2/Net.hsc view
@@ -1,19 +1,28 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Net where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Oid import Bindings.Libgit2.Types+{- typedef enum {+ GIT_DIRECTION_FETCH = 0, GIT_DIRECTION_PUSH = 1+ } git_direction; -}+#integral_t git_direction+#num GIT_DIRECTION_FETCH+#num GIT_DIRECTION_PUSH {- struct git_remote_head {- int local : 1; git_oid oid; git_oid loid; char * name;+ int local; git_oid oid; git_oid loid; char * name; }; -} #starttype git_remote_head--- #field local , CInt+#field local , CInt #field oid , <git_oid> #field loid , <git_oid> #field name , CString #stoptype-{- typedef int (* git_headlist_cb)(git_remote_head *, void *); -}-#synonym_t git_headlist_cb , CInt+{- typedef int (* git_headlist_cb)(git_remote_head * rhead,+ void * payload); -}+#callback git_headlist_cb , Ptr (<git_remote_head>) -> Ptr () -> IO CInt
Bindings/Libgit2/Notes.hsc view
@@ -1,23 +1,27 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Notes where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Oid import Bindings.Libgit2.Types+{- typedef int (* git_note_foreach_cb)(const git_oid * blob_id,+ const git_oid * annotated_object_id,+ void * payload); -}+#callback git_note_foreach_cb , Ptr (<git_oid>) -> Ptr (<git_oid>) -> Ptr () -> Ptr CInt+{- typedef struct git_iterator git_note_iterator; -}+#opaque_t git_iterator+#synonym_t git_note_iterator , <git_iterator>+#ccall git_note_iterator_new , Ptr (Ptr <git_iterator>) -> Ptr <git_repository> -> CString -> IO (CInt)+#ccall git_note_iterator_free , Ptr <git_iterator> -> IO ()+#ccall git_note_next , Ptr <git_oid> -> Ptr <git_oid> -> Ptr <git_iterator> -> IO (CInt) #ccall git_note_read , Ptr (Ptr <git_note>) -> Ptr <git_repository> -> CString -> Ptr <git_oid> -> IO (CInt) #ccall git_note_message , Ptr <git_note> -> IO (CString) #ccall git_note_oid , Ptr <git_note> -> IO (Ptr <git_oid>)-#ccall git_note_create , Ptr <git_oid> -> Ptr <git_repository> -> Ptr <git_signature> -> Ptr <git_signature> -> CString -> Ptr <git_oid> -> CString -> IO (CInt)+#ccall git_note_create , Ptr <git_oid> -> Ptr <git_repository> -> Ptr <git_signature> -> Ptr <git_signature> -> CString -> Ptr <git_oid> -> CString -> CInt -> IO (CInt) #ccall git_note_remove , Ptr <git_repository> -> CString -> Ptr <git_signature> -> Ptr <git_signature> -> Ptr <git_oid> -> IO (CInt) #ccall git_note_free , Ptr <git_note> -> IO () #ccall git_note_default_ref , Ptr (CString) -> Ptr <git_repository> -> IO (CInt)-{- typedef struct {- git_oid blob_oid; git_oid annotated_object_oid;- } git_note_data; -}-#starttype git_note_data-#field blob_oid , <git_oid>-#field annotated_object_oid , <git_oid>-#stoptype-#callback git_note_foreach_callback , Ptr <git_note_data> -> Ptr () -> IO CInt-#ccall git_note_foreach , Ptr <git_repository> -> CString -> <git_note_foreach_callback> -> Ptr () -> IO (CInt)+#ccall git_note_foreach , Ptr <git_repository> -> CString -> <git_note_foreach_cb> -> Ptr () -> IO (CInt)
Bindings/Libgit2/Object.hsc view
@@ -1,13 +1,15 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Object where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid #ccall git_object_lookup , Ptr (Ptr <git_object>) -> Ptr <git_repository> -> Ptr <git_oid> -> <git_otype> -> IO (CInt)-#ccall git_object_lookup_prefix , Ptr (Ptr <git_object>) -> Ptr <git_repository> -> Ptr <git_oid> -> CUInt -> <git_otype> -> IO (CInt)+#ccall git_object_lookup_prefix , Ptr (Ptr <git_object>) -> Ptr <git_repository> -> Ptr <git_oid> -> CSize -> <git_otype> -> IO (CInt) #ccall git_object_id , Ptr <git_object> -> IO (Ptr <git_oid>) #ccall git_object_type , Ptr <git_object> -> IO (<git_otype>) #ccall git_object_owner , Ptr <git_object> -> IO (Ptr <git_repository>)@@ -16,3 +18,5 @@ #ccall git_object_string2type , CString -> IO (<git_otype>) #ccall git_object_typeisloose , <git_otype> -> IO (CInt) #ccall git_object__size , <git_otype> -> IO (CSize)+#ccall git_object_peel , Ptr (Ptr <git_object>) -> Ptr <git_object> -> <git_otype> -> IO (CInt)+#ccall git_object_dup , Ptr (Ptr <git_object>) -> Ptr <git_object> -> IO (CInt)
Bindings/Libgit2/Odb.hsc view
@@ -1,24 +1,31 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Odb where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid import Bindings.Libgit2.OdbBackend+import Bindings.Libgit2.Indexer #ccall git_odb_new , Ptr (Ptr <git_odb>) -> IO (CInt) #ccall git_odb_open , Ptr (Ptr <git_odb>) -> CString -> IO (CInt) #ccall git_odb_add_backend , Ptr <git_odb> -> Ptr <git_odb_backend> -> CInt -> IO (CInt) #ccall git_odb_add_alternate , Ptr <git_odb> -> Ptr <git_odb_backend> -> CInt -> IO (CInt)+#ccall git_odb_add_disk_alternate , Ptr <git_odb> -> CString -> IO (CInt) #ccall git_odb_free , Ptr <git_odb> -> IO () #ccall git_odb_read , Ptr (Ptr <git_odb_object>) -> Ptr <git_odb> -> Ptr <git_oid> -> IO (CInt)-#ccall git_odb_read_prefix , Ptr (Ptr <git_odb_object>) -> Ptr <git_odb> -> Ptr <git_oid> -> CUInt -> IO (CInt)+#ccall git_odb_read_prefix , Ptr (Ptr <git_odb_object>) -> Ptr <git_odb> -> Ptr <git_oid> -> CSize -> IO (CInt) #ccall git_odb_read_header , Ptr CSize -> Ptr <git_otype> -> Ptr <git_odb> -> Ptr <git_oid> -> IO (CInt) #ccall git_odb_exists , Ptr <git_odb> -> Ptr <git_oid> -> IO (CInt)+#ccall git_odb_refresh , Ptr <git_odb> -> IO (CInt)+#ccall git_odb_foreach , Ptr <git_odb> -> <git_odb_foreach_cb> -> Ptr () -> IO (CInt) #ccall git_odb_write , Ptr <git_oid> -> Ptr <git_odb> -> Ptr () -> CSize -> <git_otype> -> IO (CInt) #ccall git_odb_open_wstream , Ptr (Ptr <git_odb_stream>) -> Ptr <git_odb> -> CSize -> <git_otype> -> IO (CInt) #ccall git_odb_open_rstream , Ptr (Ptr <git_odb_stream>) -> Ptr <git_odb> -> Ptr <git_oid> -> IO (CInt)+#ccall git_odb_write_pack , Ptr (Ptr <git_odb_writepack>) -> Ptr <git_odb> -> <git_transfer_progress_callback> -> Ptr () -> IO (CInt) #ccall git_odb_hash , Ptr <git_oid> -> Ptr () -> CSize -> <git_otype> -> IO (CInt) #ccall git_odb_hashfile , Ptr <git_oid> -> CString -> <git_otype> -> IO (CInt) #ccall git_odb_object_free , Ptr <git_odb_object> -> IO ()
Bindings/Libgit2/OdbBackend.hsc view
@@ -1,12 +1,23 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.OdbBackend where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid+import Bindings.Libgit2.Indexer+{- struct git_odb_stream; -}+{- #opaque_t git_odb_stream -}+{- struct git_odb_writepack; -}+{- #opaque_t git_odb_writepack -}+{- typedef int (* git_odb_foreach_cb)(const git_oid * id,+ void * payload); -}+#callback git_odb_foreach_cb , Ptr (<git_oid>) -> Ptr () -> IO CInt {- struct git_odb_backend {+ unsigned int version; git_odb * odb; int (* read)(void * *, size_t *,@@ -19,7 +30,7 @@ git_otype *, struct git_odb_backend *, const git_oid *,- unsigned int);+ size_t); int (* read_header)(size_t *, git_otype *, struct git_odb_backend *,@@ -37,17 +48,29 @@ struct git_odb_backend *, const git_oid *); int (* exists)(struct git_odb_backend *, const git_oid *);+ int (* refresh)(struct git_odb_backend *);+ int (* foreach)(struct git_odb_backend *,+ git_odb_foreach_cb cb,+ void * payload);+ int (* writepack)(struct git_odb_writepack * *,+ struct git_odb_backend *,+ git_transfer_progress_callback progress_cb,+ void * progress_payload); void (* free)(struct git_odb_backend *); }; -} #callback git_odb_backend_read_callback , Ptr (Ptr ()) -> Ptr CSize -> Ptr <git_otype> -> Ptr <git_odb_backend> -> Ptr <git_oid> -> IO CInt-#callback git_odb_backend_read_prefix_callback , Ptr <git_oid> -> Ptr (Ptr ()) -> Ptr CSize -> Ptr <git_otype> -> Ptr <git_odb_backend> -> Ptr <git_oid> -> CUInt -> IO CInt+#callback git_odb_backend_read_prefix_callback , Ptr <git_oid> -> Ptr (Ptr ()) -> Ptr CSize -> Ptr <git_otype> -> Ptr <git_odb_backend> -> Ptr <git_oid> -> CSize -> IO CInt #callback git_odb_backend_read_header_callback , Ptr CSize -> Ptr <git_otype> -> Ptr <git_odb_backend> -> Ptr <git_oid> -> IO CInt #callback git_odb_backend_write_callback , Ptr <git_oid> -> Ptr <git_odb_backend> -> Ptr () -> CSize -> <git_otype> -> IO CInt #callback git_odb_backend_writestream_callback , Ptr (Ptr <git_odb_stream>) -> Ptr <git_odb_backend> -> CSize -> <git_otype> -> IO CInt #callback git_odb_backend_readstream_callback , Ptr (Ptr <git_odb_stream>) -> Ptr <git_odb_backend> -> Ptr <git_oid> -> IO CInt #callback git_odb_backend_exists_callback , Ptr <git_odb_backend> -> Ptr <git_oid> -> IO CInt+#callback git_odb_backend_refresh_callback , Ptr <git_odb_backend> -> IO CInt+#callback git_odb_backend_foreach_callback , Ptr <git_odb_backend> -> <git_odb_foreach_cb> -> Ptr () -> IO CInt+#callback git_odb_backend_writepack_callback , Ptr (Ptr <git_odb_writepack>) -> Ptr <git_odb_backend> -> <git_transfer_progress_callback> -> Ptr () -> IO CInt #callback git_odb_backend_free_callback , Ptr <git_odb_backend> -> IO () #starttype git_odb_backend+#field version , CUInt #field odb , Ptr <git_odb> #field read , <git_odb_backend_read_callback> #field read_prefix , <git_odb_backend_read_prefix_callback>@@ -56,6 +79,9 @@ #field writestream , <git_odb_backend_writestream_callback> #field readstream , <git_odb_backend_readstream_callback> #field exists , <git_odb_backend_exists_callback>+#field refresh , <git_odb_backend_refresh_callback>+#field foreach , <git_odb_backend_foreach_callback>+#field writepack , <git_odb_backend_writepack_callback> #field free , <git_odb_backend_free_callback> #stoptype {- enum {@@ -68,7 +94,7 @@ #num GIT_STREAM_RW {- struct git_odb_stream { struct git_odb_backend * backend;- int mode;+ unsigned int mode; int (* read)(struct git_odb_stream * stream, char * buffer, size_t len);@@ -85,11 +111,32 @@ #callback git_odb_stream_free_callback , Ptr <git_odb_stream> -> IO () #starttype git_odb_stream #field backend , Ptr <git_odb_backend>-#field mode , CInt+#field mode , CUInt #field read , <git_odb_stream_read_callback> #field write , <git_odb_stream_write_callback> #field finalize_write , <git_odb_stream_finalize_write_callback> #field free , <git_odb_stream_free_callback> #stoptype+{- struct git_odb_writepack {+ struct git_odb_backend * backend;+ int (* add)(struct git_odb_writepack * writepack,+ const void * data,+ size_t size,+ git_transfer_progress * stats);+ int (* commit)(struct git_odb_writepack * writepack,+ git_transfer_progress * stats);+ void (* free)(struct git_odb_writepack * writepack);+}; -}+#callback git_odb_writepack_add_callback , Ptr <git_odb_writepack> -> Ptr () -> CSize -> Ptr <git_transfer_progress> -> IO CInt+#callback git_odb_writepack_commit_callback , Ptr <git_odb_writepack> -> Ptr <git_transfer_progress> -> IO CInt+#callback git_odb_writepack_free_callback , Ptr <git_odb_writepack> -> IO ()+#starttype git_odb_writepack+#field backend , Ptr <git_odb_backend>+#field add , <git_odb_writepack_add_callback>+#field commit , <git_odb_writepack_commit_callback>+#field free , <git_odb_writepack_free_callback>+#stoptype+#ccall git_odb_backend_malloc , Ptr <git_odb_backend> -> CSize -> IO (Ptr ()) #ccall git_odb_backend_pack , Ptr (Ptr <git_odb_backend>) -> CString -> IO (CInt) #ccall git_odb_backend_loose , Ptr (Ptr <git_odb_backend>) -> CString -> CInt -> CInt -> IO (CInt)+#ccall git_odb_backend_one_pack , Ptr (Ptr <git_odb_backend>) -> CString -> IO (CInt)
Bindings/Libgit2/Oid.hsc view
@@ -1,20 +1,20 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Oid where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types-#num GIT_OID_RAWSZ-#num GIT_OID_HEXSZ-#num GIT_OID_MINPREFIXLEN-{- struct _git_oid {- unsigned char id[20];-}; -}+{- typedef struct git_oid {+ unsigned char id[20];+ } git_oid; -} #starttype git_oid #array_field id , CUChar #stoptype #ccall git_oid_fromstr , Ptr <git_oid> -> CString -> IO (CInt)+#ccall git_oid_fromstrp , Ptr <git_oid> -> CString -> IO (CInt) #ccall git_oid_fromstrn , Ptr <git_oid> -> CString -> CSize -> IO (CInt) #ccall git_oid_fromraw , Ptr <git_oid> -> Ptr CUChar -> IO () #ccall git_oid_fmt , CString -> Ptr <git_oid> -> IO ()@@ -22,8 +22,9 @@ #ccall git_oid_allocfmt , Ptr <git_oid> -> IO (CString) #ccall git_oid_tostr , CString -> CSize -> Ptr <git_oid> -> IO (CString) #ccall git_oid_cpy , Ptr <git_oid> -> Ptr <git_oid> -> IO ()-#ccall git_oid_cmp , Ptr <git_oid> -> Ptr <git_oid> -> IO (CInt)-#ccall git_oid_ncmp , Ptr <git_oid> -> Ptr <git_oid> -> CUInt -> IO (CInt)+#cinline git_oid_cmp , Ptr <git_oid> -> Ptr <git_oid> -> IO (CInt)+#cinline git_oid_equal , Ptr <git_oid> -> Ptr <git_oid> -> IO (CInt)+#ccall git_oid_ncmp , Ptr <git_oid> -> Ptr <git_oid> -> CSize -> IO (CInt) #ccall git_oid_streq , Ptr <git_oid> -> CString -> IO (CInt) #ccall git_oid_iszero , Ptr <git_oid> -> IO (CInt) {- typedef struct git_oid_shorten git_oid_shorten; -}
+ Bindings/Libgit2/Oid.hsc.helper.c view
@@ -0,0 +1,5 @@+#include <bindings.cmacros.h>+#include <git2.h>++BC_INLINE2(git_oid_cmp, const git_oid*, const git_oid*, int)+BC_INLINE2(git_oid_equal, const git_oid*, const git_oid*, int)
+ Bindings/Libgit2/Pack.hsc view
@@ -0,0 +1,24 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/pack.h>+module Bindings.Libgit2.Pack where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+import Bindings.Libgit2.Oid+#ccall git_packbuilder_new , Ptr (Ptr <git_packbuilder>) -> Ptr <git_repository> -> IO (CInt)+#ccall git_packbuilder_set_threads , Ptr <git_packbuilder> -> CUInt -> IO (CUInt)+#ccall git_packbuilder_insert , Ptr <git_packbuilder> -> Ptr <git_oid> -> CString -> IO (CInt)+#ccall git_packbuilder_insert_tree , Ptr <git_packbuilder> -> Ptr <git_oid> -> IO (CInt)+#ccall git_packbuilder_write , Ptr <git_packbuilder> -> CString -> IO (CInt)+{- typedef int (* git_packbuilder_foreach_cb)(void * buf,+ size_t size,+ void * payload); -}+#callback git_packbuilder_foreach_cb , Ptr () -> CSize -> Ptr () -> IO CInt+#ccall git_packbuilder_foreach , Ptr <git_packbuilder> -> <git_packbuilder_foreach_cb> -> Ptr () -> IO (CInt)+#ccall git_packbuilder_object_count , Ptr <git_packbuilder> -> IO (CUInt)+#ccall git_packbuilder_written , Ptr <git_packbuilder> -> IO (CUInt)+#ccall git_packbuilder_free , Ptr <git_packbuilder> -> IO ()
+ Bindings/Libgit2/Push.hsc view
@@ -0,0 +1,25 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/push.h>+module Bindings.Libgit2.Push where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+{- typedef struct {+ unsigned int version; unsigned int pb_parallelism;+ } git_push_options; -}+#starttype git_push_options+#field version , CUInt+#field pb_parallelism , CUInt+#stoptype+#ccall git_push_new , Ptr (Ptr <git_push>) -> Ptr <git_remote> -> IO (CInt)+#ccall git_push_set_options , Ptr <git_push> -> Ptr <git_push_options> -> IO (CInt)+#ccall git_push_add_refspec , Ptr <git_push> -> CString -> IO (CInt)+#ccall git_push_update_tips , Ptr <git_push> -> IO (CInt)+#ccall git_push_finish , Ptr <git_push> -> IO (CInt)+#ccall git_push_unpack_ok , Ptr <git_push> -> IO (CInt)+#ccall git_push_status_foreach , Ptr <git_push> -> FunPtr (CString -> CString -> Ptr () -> CInt) -> Ptr () -> IO (CInt)+#ccall git_push_free , Ptr <git_push> -> IO ()
+ Bindings/Libgit2/Refdb.hsc view
@@ -0,0 +1,19 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/refdb.h>+module Bindings.Libgit2.Refdb where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+import Bindings.Libgit2.Oid+import Bindings.Libgit2.Refs+import Bindings.Libgit2.RefdbBackend+#ccall git_reference__alloc , Ptr <git_refdb> -> CString -> Ptr <git_oid> -> CString -> IO (Ptr <git_reference>)+#ccall git_refdb_new , Ptr (Ptr <git_refdb>) -> Ptr <git_repository> -> IO (CInt)+#ccall git_refdb_open , Ptr (Ptr <git_refdb>) -> Ptr <git_repository> -> IO (CInt)+#ccall git_refdb_compress , Ptr <git_refdb> -> IO (CInt)+#ccall git_refdb_free , Ptr <git_refdb> -> IO ()+#ccall git_refdb_set_backend , Ptr <git_refdb> -> Ptr <git_refdb_backend> -> IO (CInt)
+ Bindings/Libgit2/RefdbBackend.hsc view
@@ -0,0 +1,56 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/refdb_backend.h>+module Bindings.Libgit2.RefdbBackend where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+import Bindings.Libgit2.Refs+import Bindings.Libgit2.Oid+{- struct git_refdb_backend {+ unsigned int version;+ int (* exists)(int * exists,+ struct git_refdb_backend * backend,+ const char * ref_name);+ int (* lookup)(git_reference * * out,+ struct git_refdb_backend * backend,+ const char * ref_name);+ int (* foreach)(struct git_refdb_backend * backend,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void * payload);+ int (* foreach_glob)(struct git_refdb_backend * backend,+ const char * glob,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void * payload);+ int (* write)(struct git_refdb_backend * backend,+ const git_reference * ref);+ int (* delete)(struct git_refdb_backend * backend,+ const git_reference * ref);+ int (* compress)(struct git_refdb_backend * backend);+ void (* free)(struct git_refdb_backend * backend);+}; -}+#callback git_refdb_backend_exists_callback , Ptr CInt -> Ptr <git_refdb_backend> -> CString -> IO CInt+#callback git_refdb_backend_lookup_callback , Ptr (Ptr <git_reference>) -> Ptr <git_refdb_backend> -> CString -> IO CInt+#callback git_refdb_backend_foreach_callback , Ptr <git_refdb_backend> -> CUInt -> <git_reference_foreach_cb> -> Ptr () -> IO CInt+#callback git_refdb_backend_foreach_glob_callback , Ptr <git_refdb_backend> -> CString -> CUInt -> <git_reference_foreach_cb> -> Ptr () -> IO CInt+#callback git_refdb_backend_write_callback , Ptr <git_refdb_backend> -> Ptr <git_reference> -> IO CInt+#callback git_refdb_backend_delete_callback , Ptr <git_refdb_backend> -> Ptr <git_reference> -> IO CInt+#callback git_refdb_backend_compress_callback , Ptr <git_refdb_backend> -> IO CInt+#callback git_refdb_backend_free_callback , Ptr <git_refdb_backend> -> IO ()+#starttype git_refdb_backend+#field version , CUInt+#field exists , <git_refdb_backend_exists_callback>+#field lookup , <git_refdb_backend_lookup_callback>+#field foreach , <git_refdb_backend_foreach_callback>+#field foreach_glob , <git_refdb_backend_foreach_glob_callback>+#field write , <git_refdb_backend_write_callback>+#field delete , <git_refdb_backend_delete_callback>+#field compress , <git_refdb_backend_compress_callback>+#field free , <git_refdb_backend_free_callback>+#stoptype+#ccall git_refdb_backend_fs , Ptr (Ptr <git_refdb_backend>) -> Ptr <git_repository> -> Ptr <git_refdb> -> IO (CInt)
Bindings/Libgit2/Reflog.hsc view
@@ -1,19 +1,23 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Reflog where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid #ccall git_reflog_read , Ptr (Ptr <git_reflog>) -> Ptr <git_reference> -> IO (CInt)-#ccall git_reflog_write , Ptr <git_reference> -> Ptr <git_oid> -> Ptr <git_signature> -> CString -> IO (CInt)+#ccall git_reflog_write , Ptr <git_reflog> -> IO (CInt)+#ccall git_reflog_append , Ptr <git_reflog> -> Ptr <git_oid> -> Ptr <git_signature> -> CString -> IO (CInt) #ccall git_reflog_rename , Ptr <git_reference> -> CString -> IO (CInt) #ccall git_reflog_delete , Ptr <git_reference> -> IO (CInt)-#ccall git_reflog_entrycount , Ptr <git_reflog> -> IO (CUInt)-#ccall git_reflog_entry_byindex , Ptr <git_reflog> -> CUInt -> IO (Ptr <git_reflog_entry>)-#ccall git_reflog_entry_oidold , Ptr <git_reflog_entry> -> IO (Ptr <git_oid>)-#ccall git_reflog_entry_oidnew , Ptr <git_reflog_entry> -> IO (Ptr <git_oid>)+#ccall git_reflog_entrycount , Ptr <git_reflog> -> IO (CSize)+#ccall git_reflog_entry_byindex , Ptr <git_reflog> -> CSize -> IO (Ptr <git_reflog_entry>)+#ccall git_reflog_drop , Ptr <git_reflog> -> CSize -> CInt -> IO (CInt)+#ccall git_reflog_entry_id_old , Ptr <git_reflog_entry> -> IO (Ptr <git_oid>)+#ccall git_reflog_entry_id_new , Ptr <git_reflog_entry> -> IO (Ptr <git_oid>) #ccall git_reflog_entry_committer , Ptr <git_reflog_entry> -> IO (Ptr <git_signature>)-#ccall git_reflog_entry_msg , Ptr <git_reflog_entry> -> IO (CString)+#ccall git_reflog_entry_message , Ptr <git_reflog_entry> -> IO (CString) #ccall git_reflog_free , Ptr <git_reflog> -> IO ()
Bindings/Libgit2/Refs.hsc view
@@ -1,30 +1,48 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Refs where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid+import Bindings.Libgit2.Strarray #ccall git_reference_lookup , Ptr (Ptr <git_reference>) -> Ptr <git_repository> -> CString -> IO (CInt)-#ccall git_reference_name_to_oid , Ptr <git_oid> -> Ptr <git_repository> -> CString -> IO (CInt)-#ccall git_reference_create_symbolic , Ptr (Ptr <git_reference>) -> Ptr <git_repository> -> CString -> CString -> CInt -> IO (CInt)-#ccall git_reference_create_oid , Ptr (Ptr <git_reference>) -> Ptr <git_repository> -> CString -> Ptr <git_oid> -> CInt -> IO (CInt)-#ccall git_reference_oid , Ptr <git_reference> -> IO (Ptr <git_oid>)-#ccall git_reference_target , Ptr <git_reference> -> IO (CString)+#ccall git_reference_name_to_id , Ptr <git_oid> -> Ptr <git_repository> -> CString -> IO (CInt)+#ccall git_reference_symbolic_create , Ptr (Ptr <git_reference>) -> Ptr <git_repository> -> CString -> CString -> CInt -> IO (CInt)+#ccall git_reference_create , Ptr (Ptr <git_reference>) -> Ptr <git_repository> -> CString -> Ptr <git_oid> -> CInt -> IO (CInt)+#ccall git_reference_target , Ptr <git_reference> -> IO (Ptr <git_oid>)+#ccall git_reference_symbolic_target , Ptr <git_reference> -> IO (CString) #ccall git_reference_type , Ptr <git_reference> -> IO (<git_ref_t>) #ccall git_reference_name , Ptr <git_reference> -> IO (CString) #ccall git_reference_resolve , Ptr (Ptr <git_reference>) -> Ptr <git_reference> -> IO (CInt) #ccall git_reference_owner , Ptr <git_reference> -> IO (Ptr <git_repository>)-#ccall git_reference_set_target , Ptr <git_reference> -> CString -> IO (CInt)-#ccall git_reference_set_oid , Ptr <git_reference> -> Ptr <git_oid> -> IO (CInt)-#ccall git_reference_rename , Ptr <git_reference> -> CString -> CInt -> IO (CInt)+#ccall git_reference_symbolic_set_target , Ptr (Ptr <git_reference>) -> Ptr <git_reference> -> CString -> IO (CInt)+#ccall git_reference_set_target , Ptr (Ptr <git_reference>) -> Ptr <git_reference> -> Ptr <git_oid> -> IO (CInt)+#ccall git_reference_rename , Ptr (Ptr <git_reference>) -> Ptr <git_reference> -> CString -> CInt -> IO (CInt) #ccall git_reference_delete , Ptr <git_reference> -> IO (CInt)-#ccall git_reference_packall , Ptr <git_repository> -> IO (CInt) #ccall git_reference_list , Ptr <git_strarray> -> Ptr <git_repository> -> CUInt -> IO (CInt)-#callback git_reference_foreach_callback , CString -> Ptr () -> IO CInt-#ccall git_reference_foreach , Ptr <git_repository> -> CUInt -> <git_reference_foreach_callback> -> Ptr () -> IO (CInt)-#ccall git_reference_is_packed , Ptr <git_reference> -> IO (CInt)-#ccall git_reference_reload , Ptr <git_reference> -> IO (CInt)+{- typedef int (* git_reference_foreach_cb)(const char * refname,+ void * payload); -}+#callback git_reference_foreach_cb , CString -> Ptr () -> Ptr CInt+#ccall git_reference_foreach , Ptr <git_repository> -> CUInt -> <git_reference_foreach_cb> -> Ptr () -> IO (CInt) #ccall git_reference_free , Ptr <git_reference> -> IO () #ccall git_reference_cmp , Ptr <git_reference> -> Ptr <git_reference> -> IO (CInt)+#ccall git_reference_foreach_glob , Ptr <git_repository> -> CString -> CUInt -> <git_reference_foreach_cb> -> Ptr () -> IO (CInt)+#ccall git_reference_has_log , Ptr <git_reference> -> IO (CInt)+#ccall git_reference_is_branch , Ptr <git_reference> -> IO (CInt)+#ccall git_reference_is_remote , Ptr <git_reference> -> IO (CInt)+{- typedef enum {+ GIT_REF_FORMAT_NORMAL = 0,+ GIT_REF_FORMAT_ALLOW_ONELEVEL = 1 << 0,+ GIT_REF_FORMAT_REFSPEC_PATTERN = 1 << 1+ } git_reference_normalize_t; -}+#integral_t git_reference_normalize_t+#num GIT_REF_FORMAT_NORMAL+#num GIT_REF_FORMAT_ALLOW_ONELEVEL+#num GIT_REF_FORMAT_REFSPEC_PATTERN+#ccall git_reference_normalize_name , CString -> CSize -> CString -> CUInt -> IO (CInt)+#ccall git_reference_peel , Ptr (Ptr <git_object>) -> Ptr <git_reference> -> <git_otype> -> IO (CInt)+#ccall git_reference_is_valid_name , CString -> IO (CInt)
Bindings/Libgit2/Refspec.hsc view
@@ -1,11 +1,16 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Refspec where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types #ccall git_refspec_src , Ptr <git_refspec> -> IO (CString) #ccall git_refspec_dst , Ptr <git_refspec> -> IO (CString)+#ccall git_refspec_force , Ptr <git_refspec> -> IO (CInt) #ccall git_refspec_src_matches , Ptr <git_refspec> -> CString -> IO (CInt)+#ccall git_refspec_dst_matches , Ptr <git_refspec> -> CString -> IO (CInt) #ccall git_refspec_transform , CString -> CSize -> Ptr <git_refspec> -> CString -> IO (CInt)+#ccall git_refspec_rtransform , CString -> CSize -> Ptr <git_refspec> -> CString -> IO (CInt)
Bindings/Libgit2/Remote.hsc view
@@ -1,6 +1,8 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Remote where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common@@ -8,26 +10,85 @@ import Bindings.Libgit2.Refspec import Bindings.Libgit2.Net import Bindings.Libgit2.Indexer-import Bindings.Libgit2.Oid+import Bindings.Libgit2.Strarray+import Bindings.Libgit2.Transport import Bindings.Libgit2.Types-#ccall git_remote_new , Ptr (Ptr <git_remote>) -> Ptr <git_repository> -> CString -> CString -> CString -> IO (CInt)+import Bindings.Libgit2.Oid+{- typedef int (* git_remote_rename_problem_cb)(const char * problematic_refspec,+ void * payload); -}+#callback git_remote_rename_problem_cb , CString -> Ptr () -> Ptr CInt+#ccall git_remote_create , Ptr (Ptr <git_remote>) -> Ptr <git_repository> -> CString -> CString -> IO (CInt)+#ccall git_remote_create_inmemory , Ptr (Ptr <git_remote>) -> Ptr <git_repository> -> CString -> CString -> IO (CInt) #ccall git_remote_load , Ptr (Ptr <git_remote>) -> Ptr <git_repository> -> CString -> IO (CInt) #ccall git_remote_save , Ptr <git_remote> -> IO (CInt) #ccall git_remote_name , Ptr <git_remote> -> IO (CString) #ccall git_remote_url , Ptr <git_remote> -> IO (CString)+#ccall git_remote_pushurl , Ptr <git_remote> -> IO (CString)+#ccall git_remote_set_url , Ptr <git_remote> -> CString -> IO (CInt)+#ccall git_remote_set_pushurl , Ptr <git_remote> -> CString -> IO (CInt) #ccall git_remote_set_fetchspec , Ptr <git_remote> -> CString -> IO (CInt) #ccall git_remote_fetchspec , Ptr <git_remote> -> IO (Ptr <git_refspec>) #ccall git_remote_set_pushspec , Ptr <git_remote> -> CString -> IO (CInt) #ccall git_remote_pushspec , Ptr <git_remote> -> IO (Ptr <git_refspec>)-#ccall git_remote_connect , Ptr <git_remote> -> CInt -> IO (CInt)-#ccall git_remote_ls , Ptr <git_remote> -> CInt -> Ptr () -> IO (CInt)-#ccall git_remote_download , Ptr <git_remote> -> Ptr CLong -> Ptr <git_indexer_stats> -> IO (CInt)+#ccall git_remote_connect , Ptr <git_remote> -> <git_direction> -> IO (CInt)+#ccall git_remote_ls , Ptr <git_remote> -> <git_headlist_cb> -> Ptr () -> IO (CInt)+#ccall git_remote_download , Ptr <git_remote> -> <git_transfer_progress_callback> -> Ptr () -> IO (CInt) #ccall git_remote_connected , Ptr <git_remote> -> IO (CInt)+#ccall git_remote_stop , Ptr <git_remote> -> IO () #ccall git_remote_disconnect , Ptr <git_remote> -> IO () #ccall git_remote_free , Ptr <git_remote> -> IO ()-#callback git_remote_update_tips_callback , CString -> Ptr <git_oid> -> Ptr <git_oid> -> IO CInt-#ccall git_remote_update_tips , Ptr <git_remote> -> <git_remote_update_tips_callback> -> IO (CInt)+#ccall git_remote_update_tips , Ptr <git_remote> -> IO (CInt) #ccall git_remote_valid_url , CString -> IO (CInt) #ccall git_remote_supported_url , CString -> IO (CInt) #ccall git_remote_list , Ptr <git_strarray> -> Ptr <git_repository> -> IO (CInt)-#ccall git_remote_add , Ptr (Ptr <git_remote>) -> Ptr <git_repository> -> CString -> CString -> IO (CInt)+#ccall git_remote_check_cert , Ptr <git_remote> -> CInt -> IO ()+#ccall git_remote_set_cred_acquire_cb , Ptr <git_remote> -> <git_cred_acquire_cb> -> Ptr () -> IO ()+#ccall git_remote_set_transport , Ptr <git_remote> -> Ptr <git_transport> -> IO (CInt)+{- typedef enum git_remote_completion_type {+ GIT_REMOTE_COMPLETION_DOWNLOAD,+ GIT_REMOTE_COMPLETION_INDEXING,+ GIT_REMOTE_COMPLETION_ERROR+ } git_remote_completion_type; -}+#integral_t git_remote_completion_type+#num GIT_REMOTE_COMPLETION_DOWNLOAD+#num GIT_REMOTE_COMPLETION_INDEXING+#num GIT_REMOTE_COMPLETION_ERROR+{- struct git_remote_callbacks {+ unsigned int version;+ void (* progress)(const char * str, int len, void * data);+ int (* completion)(git_remote_completion_type type, void * data);+ int (* update_tips)(const char * refname,+ const git_oid * a,+ const git_oid * b,+ void * data);+ void * payload;+}; -}+#callback git_remote_callbacks_progress_callback , CString -> CInt -> Ptr () -> IO ()+#callback git_remote_callbacks_completion_callback , <git_remote_completion_type> -> Ptr () -> IO CInt+#callback git_remote_callbacks_update_tips_callback , CString -> Ptr <git_oid> -> Ptr <git_oid> -> Ptr () -> IO CInt+#starttype git_remote_callbacks+#field version , CUInt+#field progress , <git_remote_callbacks_progress_callback>+#field completion , <git_remote_callbacks_completion_callback>+#field update_tips , <git_remote_callbacks_update_tips_callback>+#field payload , Ptr ()+#stoptype+#ccall git_remote_set_callbacks , Ptr <git_remote> -> Ptr <git_remote_callbacks> -> IO (CInt)+#ccall git_remote_stats , Ptr <git_remote> -> IO (Ptr <git_transfer_progress>)+{- typedef enum {+ GIT_REMOTE_DOWNLOAD_TAGS_UNSET,+ GIT_REMOTE_DOWNLOAD_TAGS_NONE,+ GIT_REMOTE_DOWNLOAD_TAGS_AUTO,+ GIT_REMOTE_DOWNLOAD_TAGS_ALL+ } git_remote_autotag_option_t; -}+#integral_t git_remote_autotag_option_t+#num GIT_REMOTE_DOWNLOAD_TAGS_UNSET+#num GIT_REMOTE_DOWNLOAD_TAGS_NONE+#num GIT_REMOTE_DOWNLOAD_TAGS_AUTO+#num GIT_REMOTE_DOWNLOAD_TAGS_ALL+#ccall git_remote_autotag , Ptr <git_remote> -> IO (<git_remote_autotag_option_t>)+#ccall git_remote_set_autotag , Ptr <git_remote> -> <git_remote_autotag_option_t> -> IO ()+#ccall git_remote_rename , Ptr <git_remote> -> CString -> <git_remote_rename_problem_cb> -> Ptr () -> IO (CInt)+#ccall git_remote_update_fetchhead , Ptr <git_remote> -> IO (CInt)+#ccall git_remote_set_update_fetchhead , Ptr <git_remote> -> CInt -> IO ()+#ccall git_remote_is_valid_name , CString -> IO (CInt)
Bindings/Libgit2/Repository.hsc view
@@ -1,34 +1,127 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Repository where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid #ccall git_repository_open , Ptr (Ptr <git_repository>) -> CString -> IO (CInt)+#ccall git_repository_wrap_odb , Ptr (Ptr <git_repository>) -> Ptr <git_odb> -> IO (CInt) #ccall git_repository_discover , CString -> CSize -> CString -> CInt -> CString -> IO (CInt)-{- enum {- GIT_REPOSITORY_OPEN_NO_SEARCH = 1 << 0,- GIT_REPOSITORY_OPEN_CROSS_FS = 1 << 1-}; -}+{- typedef enum {+ GIT_REPOSITORY_OPEN_NO_SEARCH = 1 << 0,+ GIT_REPOSITORY_OPEN_CROSS_FS = 1 << 1+ } git_repository_open_flag_t; -}+#integral_t git_repository_open_flag_t #num GIT_REPOSITORY_OPEN_NO_SEARCH #num GIT_REPOSITORY_OPEN_CROSS_FS #ccall git_repository_open_ext , Ptr (Ptr <git_repository>) -> CString -> CUInt -> CString -> IO (CInt) #ccall git_repository_new , Ptr (Ptr <git_repository>) -> IO (CInt) #ccall git_repository_free , Ptr <git_repository> -> IO () #ccall git_repository_init , Ptr (Ptr <git_repository>) -> CString -> CUInt -> IO (CInt)+{- typedef enum {+ GIT_REPOSITORY_INIT_BARE = 1u << 0,+ GIT_REPOSITORY_INIT_NO_REINIT = 1u << 1,+ GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = 1u << 2,+ GIT_REPOSITORY_INIT_MKDIR = 1u << 3,+ GIT_REPOSITORY_INIT_MKPATH = 1u << 4,+ GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = 1u << 5+ } git_repository_init_flag_t; -}+#integral_t git_repository_init_flag_t+#num GIT_REPOSITORY_INIT_BARE+#num GIT_REPOSITORY_INIT_NO_REINIT+#num GIT_REPOSITORY_INIT_NO_DOTGIT_DIR+#num GIT_REPOSITORY_INIT_MKDIR+#num GIT_REPOSITORY_INIT_MKPATH+#num GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE+{- typedef enum {+ GIT_REPOSITORY_INIT_SHARED_UMASK = 0,+ GIT_REPOSITORY_INIT_SHARED_GROUP = 02775,+ GIT_REPOSITORY_INIT_SHARED_ALL = 02777+ } git_repository_init_mode_t; -}+#integral_t git_repository_init_mode_t+#num GIT_REPOSITORY_INIT_SHARED_UMASK+#num GIT_REPOSITORY_INIT_SHARED_GROUP+#num GIT_REPOSITORY_INIT_SHARED_ALL+{- typedef struct {+ unsigned int version;+ uint32_t flags;+ uint32_t mode;+ const char * workdir_path;+ const char * description;+ const char * template_path;+ const char * initial_head;+ const char * origin_url;+ } git_repository_init_options; -}+#starttype git_repository_init_options+#field version , CUInt+#field flags , CUInt+#field mode , CUInt+#field workdir_path , CString+#field description , CString+#field template_path , CString+#field initial_head , CString+#field origin_url , CString+#stoptype+#ccall git_repository_init_ext , Ptr (Ptr <git_repository>) -> CString -> Ptr <git_repository_init_options> -> IO (CInt) #ccall git_repository_head , Ptr (Ptr <git_reference>) -> Ptr <git_repository> -> IO (CInt) #ccall git_repository_head_detached , Ptr <git_repository> -> IO (CInt) #ccall git_repository_head_orphan , Ptr <git_repository> -> IO (CInt) #ccall git_repository_is_empty , Ptr <git_repository> -> IO (CInt) #ccall git_repository_path , Ptr <git_repository> -> IO (CString) #ccall git_repository_workdir , Ptr <git_repository> -> IO (CString)-#ccall git_repository_set_workdir , Ptr <git_repository> -> CString -> IO (CInt)+#ccall git_repository_set_workdir , Ptr <git_repository> -> CString -> CInt -> IO (CInt) #ccall git_repository_is_bare , Ptr <git_repository> -> IO (CInt) #ccall git_repository_config , Ptr (Ptr <git_config>) -> Ptr <git_repository> -> IO (CInt) #ccall git_repository_set_config , Ptr <git_repository> -> Ptr <git_config> -> IO () #ccall git_repository_odb , Ptr (Ptr <git_odb>) -> Ptr <git_repository> -> IO (CInt) #ccall git_repository_set_odb , Ptr <git_repository> -> Ptr <git_odb> -> IO ()+#ccall git_repository_refdb , Ptr (Ptr <git_refdb>) -> Ptr <git_repository> -> IO (CInt)+#ccall git_repository_set_refdb , Ptr <git_repository> -> Ptr <git_refdb> -> IO () #ccall git_repository_index , Ptr (Ptr <git_index>) -> Ptr <git_repository> -> IO (CInt) #ccall git_repository_set_index , Ptr <git_repository> -> Ptr <git_index> -> IO ()+#ccall git_repository_message , CString -> CSize -> Ptr <git_repository> -> IO (CInt)+#ccall git_repository_message_remove , Ptr <git_repository> -> IO (CInt)+#ccall git_repository_merge_cleanup , Ptr <git_repository> -> IO (CInt)+{- typedef int (* git_repository_fetchhead_foreach_cb)(const char * ref_name,+ const char * remote_url,+ const git_oid * oid,+ unsigned int is_merge,+ void * payload); -}+#callback git_repository_fetchhead_foreach_cb , CString -> CString -> Ptr (<git_oid>) -> CUInt -> Ptr () -> IO CInt+#ccall git_repository_fetchhead_foreach , Ptr <git_repository> -> <git_repository_fetchhead_foreach_cb> -> Ptr () -> IO (CInt)+{- typedef int (* git_repository_mergehead_foreach_cb)(const git_oid * oid,+ void * payload); -}+#callback git_repository_mergehead_foreach_cb , Ptr (<git_oid>) -> Ptr () -> IO CInt+#ccall git_repository_mergehead_foreach , Ptr <git_repository> -> <git_repository_mergehead_foreach_cb> -> Ptr () -> IO (CInt)+#ccall git_repository_hashfile , Ptr <git_oid> -> Ptr <git_repository> -> CString -> <git_otype> -> CString -> IO (CInt)+#ccall git_repository_set_head , Ptr <git_repository> -> CString -> IO (CInt)+#ccall git_repository_set_head_detached , Ptr <git_repository> -> Ptr <git_oid> -> IO (CInt)+#ccall git_repository_detach_head , Ptr <git_repository> -> IO (CInt)+{- typedef enum {+ GIT_REPOSITORY_STATE_NONE,+ GIT_REPOSITORY_STATE_MERGE,+ GIT_REPOSITORY_STATE_REVERT,+ GIT_REPOSITORY_STATE_CHERRY_PICK,+ GIT_REPOSITORY_STATE_BISECT,+ GIT_REPOSITORY_STATE_REBASE,+ GIT_REPOSITORY_STATE_REBASE_INTERACTIVE,+ GIT_REPOSITORY_STATE_REBASE_MERGE,+ GIT_REPOSITORY_STATE_APPLY_MAILBOX,+ GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE+ } git_repository_state_t; -}+#integral_t git_repository_state_t+#num GIT_REPOSITORY_STATE_NONE+#num GIT_REPOSITORY_STATE_MERGE+#num GIT_REPOSITORY_STATE_REVERT+#num GIT_REPOSITORY_STATE_CHERRY_PICK+#num GIT_REPOSITORY_STATE_BISECT+#num GIT_REPOSITORY_STATE_REBASE+#num GIT_REPOSITORY_STATE_REBASE_INTERACTIVE+#num GIT_REPOSITORY_STATE_REBASE_MERGE+#num GIT_REPOSITORY_STATE_APPLY_MAILBOX+#num GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE+#ccall git_repository_state , Ptr <git_repository> -> IO (CInt)
+ Bindings/Libgit2/Reset.hsc view
@@ -0,0 +1,20 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/reset.h>+module Bindings.Libgit2.Reset where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+import Bindings.Libgit2.Strarray+{- typedef enum {+ GIT_RESET_SOFT = 1, GIT_RESET_MIXED = 2, GIT_RESET_HARD = 3+ } git_reset_t; -}+#integral_t git_reset_t+#num GIT_RESET_SOFT+#num GIT_RESET_MIXED+#num GIT_RESET_HARD+#ccall git_reset , Ptr <git_repository> -> Ptr <git_object> -> <git_reset_t> -> IO (CInt)+#ccall git_reset_default , Ptr <git_repository> -> Ptr <git_object> -> Ptr <git_strarray> -> IO (CInt)
+ Bindings/Libgit2/Revparse.hsc view
@@ -0,0 +1,29 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/revparse.h>+module Bindings.Libgit2.Revparse where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+#ccall git_revparse_single , Ptr (Ptr <git_object>) -> Ptr <git_repository> -> CString -> IO (CInt)+{- typedef enum {+ GIT_REVPARSE_SINGLE = 1 << 0,+ GIT_REVPARSE_RANGE = 1 << 1,+ GIT_REVPARSE_MERGE_BASE = 1 << 2+ } git_revparse_mode_t; -}+#integral_t git_revparse_mode_t+#num GIT_REVPARSE_SINGLE+#num GIT_REVPARSE_RANGE+#num GIT_REVPARSE_MERGE_BASE+{- typedef struct {+ git_object * from; git_object * to; unsigned int flags;+ } git_revspec; -}+#starttype git_revspec+#field from , Ptr <git_object>+#field to , Ptr <git_object>+#field flags , CUInt+#stoptype+#ccall git_revparse , Ptr <git_revspec> -> Ptr <git_repository> -> CString -> IO (CInt)
Bindings/Libgit2/Revwalk.hsc view
@@ -1,6 +1,8 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Revwalk where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common@@ -18,5 +20,6 @@ #ccall git_revwalk_hide_ref , Ptr <git_revwalk> -> CString -> IO (CInt) #ccall git_revwalk_next , Ptr <git_oid> -> Ptr <git_revwalk> -> IO (CInt) #ccall git_revwalk_sorting , Ptr <git_revwalk> -> CUInt -> IO ()+#ccall git_revwalk_push_range , Ptr <git_revwalk> -> CString -> IO (CInt) #ccall git_revwalk_free , Ptr <git_revwalk> -> IO () #ccall git_revwalk_repository , Ptr <git_revwalk> -> IO (Ptr <git_repository>)
Bindings/Libgit2/Signature.hsc view
@@ -1,6 +1,8 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Signature where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common
+ Bindings/Libgit2/Stash.hsc view
@@ -0,0 +1,30 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/stash.h>+module Bindings.Libgit2.Stash where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+import Bindings.Libgit2.Oid+{- typedef enum {+ GIT_STASH_DEFAULT = 0,+ GIT_STASH_KEEP_INDEX = 1 << 0,+ GIT_STASH_INCLUDE_UNTRACKED = 1 << 1,+ GIT_STASH_INCLUDE_IGNORED = 1 << 2+ } git_stash_flags; -}+#integral_t git_stash_flags+#num GIT_STASH_DEFAULT+#num GIT_STASH_KEEP_INDEX+#num GIT_STASH_INCLUDE_UNTRACKED+#num GIT_STASH_INCLUDE_IGNORED+#ccall git_stash_save , Ptr <git_oid> -> Ptr <git_repository> -> Ptr <git_signature> -> CString -> CUInt -> IO (CInt)+{- typedef int (* git_stash_cb)(size_t index,+ const char * message,+ const git_oid * stash_id,+ void * payload); -}+#callback git_stash_cb , CSize -> CString -> Ptr (<git_oid>) -> Ptr () -> IO CInt+#ccall git_stash_foreach , Ptr <git_repository> -> <git_stash_cb> -> Ptr () -> IO (CInt)+#ccall git_stash_drop , Ptr <git_repository> -> CSize -> IO (CInt)
Bindings/Libgit2/Status.hsc view
@@ -1,30 +1,43 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Status where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types-{- enum {- GIT_STATUS_CURRENT = 0,- GIT_STATUS_INDEX_NEW = 1 << 0,- GIT_STATUS_INDEX_MODIFIED = 1 << 1,- GIT_STATUS_INDEX_DELETED = 1 << 2,- GIT_STATUS_WT_NEW = 1 << 3,- GIT_STATUS_WT_MODIFIED = 1 << 4,- GIT_STATUS_WT_DELETED = 1 << 5,- GIT_STATUS_IGNORED = 1 << 6-}; -}+import Bindings.Libgit2.Strarray+{- typedef enum {+ GIT_STATUS_CURRENT = 0,+ GIT_STATUS_INDEX_NEW = 1u << 0,+ GIT_STATUS_INDEX_MODIFIED = 1u << 1,+ GIT_STATUS_INDEX_DELETED = 1u << 2,+ GIT_STATUS_INDEX_RENAMED = 1u << 3,+ GIT_STATUS_INDEX_TYPECHANGE = 1u << 4,+ GIT_STATUS_WT_NEW = 1u << 7,+ GIT_STATUS_WT_MODIFIED = 1u << 8,+ GIT_STATUS_WT_DELETED = 1u << 9,+ GIT_STATUS_WT_TYPECHANGE = 1u << 10,+ GIT_STATUS_IGNORED = 1u << 14+ } git_status_t; -}+#integral_t git_status_t #num GIT_STATUS_CURRENT #num GIT_STATUS_INDEX_NEW #num GIT_STATUS_INDEX_MODIFIED #num GIT_STATUS_INDEX_DELETED+#num GIT_STATUS_INDEX_RENAMED+#num GIT_STATUS_INDEX_TYPECHANGE #num GIT_STATUS_WT_NEW #num GIT_STATUS_WT_MODIFIED #num GIT_STATUS_WT_DELETED+#num GIT_STATUS_WT_TYPECHANGE #num GIT_STATUS_IGNORED-#callback git_status_foreach_callback , CString -> CUInt -> Ptr () -> IO CInt-#ccall git_status_foreach , Ptr <git_repository> -> <git_status_foreach_callback> -> Ptr () -> IO (CInt)+{- typedef int (* git_status_cb)(const char * path,+ unsigned int status_flags,+ void * payload); -}+#callback git_status_cb , CString -> CUInt -> Ptr () -> IO CInt+#ccall git_status_foreach , Ptr <git_repository> -> <git_status_cb> -> Ptr () -> IO (CInt) {- typedef enum { GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0, GIT_STATUS_SHOW_INDEX_ONLY = 1,@@ -36,27 +49,35 @@ #num GIT_STATUS_SHOW_INDEX_ONLY #num GIT_STATUS_SHOW_WORKDIR_ONLY #num GIT_STATUS_SHOW_INDEX_THEN_WORKDIR-{- enum {- GIT_STATUS_OPT_INCLUDE_UNTRACKED = 1 << 0,- GIT_STATUS_OPT_INCLUDE_IGNORED = 1 << 1,- GIT_STATUS_OPT_INCLUDE_UNMODIFIED = 1 << 2,- GIT_STATUS_OPT_EXCLUDE_SUBMODULED = 1 << 3,- GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = 1 << 4-}; -}+{- typedef enum {+ GIT_STATUS_OPT_INCLUDE_UNTRACKED = 1u << 0,+ GIT_STATUS_OPT_INCLUDE_IGNORED = 1u << 1,+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED = 1u << 2,+ GIT_STATUS_OPT_EXCLUDE_SUBMODULES = 1u << 3,+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = 1u << 4,+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = 1u << 5,+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = 1u << 6+ } git_status_opt_t; -}+#integral_t git_status_opt_t #num GIT_STATUS_OPT_INCLUDE_UNTRACKED #num GIT_STATUS_OPT_INCLUDE_IGNORED #num GIT_STATUS_OPT_INCLUDE_UNMODIFIED-#num GIT_STATUS_OPT_EXCLUDE_SUBMODULED+#num GIT_STATUS_OPT_EXCLUDE_SUBMODULES #num GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS+#num GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH+#num GIT_STATUS_OPT_RECURSE_IGNORED_DIRS {- typedef struct {- git_status_show_t show; unsigned int flags; git_strarray pathspec;+ unsigned int version;+ git_status_show_t show;+ unsigned int flags;+ git_strarray pathspec; } git_status_options; -} #starttype git_status_options+#field version , CUInt #field show , <git_status_show_t> #field flags , CUInt #field pathspec , <git_strarray> #stoptype-#callback git_status_foreach_ext_callback , CString -> CUInt -> Ptr () -> IO CInt-#ccall git_status_foreach_ext , Ptr <git_repository> -> Ptr <git_status_options> -> <git_status_foreach_ext_callback> -> Ptr () -> IO (CInt)+#ccall git_status_foreach_ext , Ptr <git_repository> -> Ptr <git_status_options> -> <git_status_cb> -> Ptr () -> IO (CInt) #ccall git_status_file , Ptr CUInt -> Ptr <git_repository> -> CString -> IO (CInt) #ccall git_status_should_ignore , Ptr CInt -> Ptr <git_repository> -> CString -> IO (CInt)
+ Bindings/Libgit2/Strarray.hsc view
@@ -0,0 +1,18 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/strarray.h>+module Bindings.Libgit2.Strarray where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+{- typedef struct git_strarray {+ char * * strings; size_t count;+ } git_strarray; -}+#starttype git_strarray+#field strings , Ptr (CString)+#field count , CSize+#stoptype+#ccall git_strarray_free , Ptr <git_strarray> -> IO ()+#ccall git_strarray_copy , Ptr <git_strarray> -> Ptr <git_strarray> -> IO (CInt)
Bindings/Libgit2/Submodule.hsc view
@@ -1,51 +1,97 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Submodule where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid+{- typedef struct git_submodule git_submodule; -}+#opaque_t git_submodule {- typedef enum {+ GIT_SUBMODULE_UPDATE_DEFAULT = -1, GIT_SUBMODULE_UPDATE_CHECKOUT = 0, GIT_SUBMODULE_UPDATE_REBASE = 1,- GIT_SUBMODULE_UPDATE_MERGE = 2+ GIT_SUBMODULE_UPDATE_MERGE = 2,+ GIT_SUBMODULE_UPDATE_NONE = 3 } git_submodule_update_t; -} #integral_t git_submodule_update_t+#num GIT_SUBMODULE_UPDATE_DEFAULT #num GIT_SUBMODULE_UPDATE_CHECKOUT #num GIT_SUBMODULE_UPDATE_REBASE #num GIT_SUBMODULE_UPDATE_MERGE+#num GIT_SUBMODULE_UPDATE_NONE {- typedef enum {- GIT_SUBMODULE_IGNORE_ALL = 0,- GIT_SUBMODULE_IGNORE_DIRTY = 1,- GIT_SUBMODULE_IGNORE_UNTRACKED = 2,- GIT_SUBMODULE_IGNORE_NONE = 3+ GIT_SUBMODULE_IGNORE_DEFAULT = -1,+ GIT_SUBMODULE_IGNORE_NONE = 0,+ GIT_SUBMODULE_IGNORE_UNTRACKED = 1,+ GIT_SUBMODULE_IGNORE_DIRTY = 2,+ GIT_SUBMODULE_IGNORE_ALL = 3 } git_submodule_ignore_t; -} #integral_t git_submodule_ignore_t-#num GIT_SUBMODULE_IGNORE_ALL-#num GIT_SUBMODULE_IGNORE_DIRTY-#num GIT_SUBMODULE_IGNORE_UNTRACKED+#num GIT_SUBMODULE_IGNORE_DEFAULT #num GIT_SUBMODULE_IGNORE_NONE-{- typedef struct {- char * name;- char * path;- char * url;- git_oid oid;- git_submodule_update_t update;- git_submodule_ignore_t ignore;- int fetch_recurse;- int refcount;- } git_submodule; -}-#starttype git_submodule-#field name , CString-#field path , CString-#field url , CString-#field oid , <git_oid>-#field update , <git_submodule_update_t>-#field ignore , <git_submodule_ignore_t>-#field fetch_recurse , CInt-#field refcount , CInt-#stoptype-#callback git_submodule_foreach_callback , CString -> Ptr () -> IO CInt-#ccall git_submodule_foreach , Ptr <git_repository> -> <git_submodule_foreach_callback> -> Ptr () -> IO (CInt)+#num GIT_SUBMODULE_IGNORE_UNTRACKED+#num GIT_SUBMODULE_IGNORE_DIRTY+#num GIT_SUBMODULE_IGNORE_ALL+{- typedef enum {+ GIT_SUBMODULE_STATUS_IN_HEAD = 1u << 0,+ GIT_SUBMODULE_STATUS_IN_INDEX = 1u << 1,+ GIT_SUBMODULE_STATUS_IN_CONFIG = 1u << 2,+ GIT_SUBMODULE_STATUS_IN_WD = 1u << 3,+ GIT_SUBMODULE_STATUS_INDEX_ADDED = 1u << 4,+ GIT_SUBMODULE_STATUS_INDEX_DELETED = 1u << 5,+ GIT_SUBMODULE_STATUS_INDEX_MODIFIED = 1u << 6,+ GIT_SUBMODULE_STATUS_WD_UNINITIALIZED = 1u << 7,+ GIT_SUBMODULE_STATUS_WD_ADDED = 1u << 8,+ GIT_SUBMODULE_STATUS_WD_DELETED = 1u << 9,+ GIT_SUBMODULE_STATUS_WD_MODIFIED = 1u << 10,+ GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED = 1u << 11,+ GIT_SUBMODULE_STATUS_WD_WD_MODIFIED = 1u << 12,+ GIT_SUBMODULE_STATUS_WD_UNTRACKED = 1u << 13+ } git_submodule_status_t; -}+#integral_t git_submodule_status_t+#num GIT_SUBMODULE_STATUS_IN_HEAD+#num GIT_SUBMODULE_STATUS_IN_INDEX+#num GIT_SUBMODULE_STATUS_IN_CONFIG+#num GIT_SUBMODULE_STATUS_IN_WD+#num GIT_SUBMODULE_STATUS_INDEX_ADDED+#num GIT_SUBMODULE_STATUS_INDEX_DELETED+#num GIT_SUBMODULE_STATUS_INDEX_MODIFIED+#num GIT_SUBMODULE_STATUS_WD_UNINITIALIZED+#num GIT_SUBMODULE_STATUS_WD_ADDED+#num GIT_SUBMODULE_STATUS_WD_DELETED+#num GIT_SUBMODULE_STATUS_WD_MODIFIED+#num GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED+#num GIT_SUBMODULE_STATUS_WD_WD_MODIFIED+#num GIT_SUBMODULE_STATUS_WD_UNTRACKED #ccall git_submodule_lookup , Ptr (Ptr <git_submodule>) -> Ptr <git_repository> -> CString -> IO (CInt)+#callback git_submodule_foreach_callback , Ptr (<git_submodule>) -> CString -> Ptr () -> IO CInt+#ccall git_submodule_foreach , Ptr <git_repository> -> <git_submodule_foreach_callback> -> Ptr () -> IO (CInt)+#ccall git_submodule_add_setup , Ptr (Ptr <git_submodule>) -> Ptr <git_repository> -> CString -> CString -> CInt -> IO (CInt)+#ccall git_submodule_add_finalize , Ptr <git_submodule> -> IO (CInt)+#ccall git_submodule_add_to_index , Ptr <git_submodule> -> CInt -> IO (CInt)+#ccall git_submodule_save , Ptr <git_submodule> -> IO (CInt)+#ccall git_submodule_owner , Ptr <git_submodule> -> IO (Ptr <git_repository>)+#ccall git_submodule_name , Ptr <git_submodule> -> IO (CString)+#ccall git_submodule_path , Ptr <git_submodule> -> IO (CString)+#ccall git_submodule_url , Ptr <git_submodule> -> IO (CString)+#ccall git_submodule_set_url , Ptr <git_submodule> -> CString -> IO (CInt)+#ccall git_submodule_index_id , Ptr <git_submodule> -> IO (Ptr <git_oid>)+#ccall git_submodule_head_id , Ptr <git_submodule> -> IO (Ptr <git_oid>)+#ccall git_submodule_wd_id , Ptr <git_submodule> -> IO (Ptr <git_oid>)+#ccall git_submodule_ignore , Ptr <git_submodule> -> IO (<git_submodule_ignore_t>)+#ccall git_submodule_set_ignore , Ptr <git_submodule> -> <git_submodule_ignore_t> -> IO (<git_submodule_ignore_t>)+#ccall git_submodule_update , Ptr <git_submodule> -> IO (<git_submodule_update_t>)+#ccall git_submodule_set_update , Ptr <git_submodule> -> <git_submodule_update_t> -> IO (<git_submodule_update_t>)+#ccall git_submodule_fetch_recurse_submodules , Ptr <git_submodule> -> IO (CInt)+#ccall git_submodule_set_fetch_recurse_submodules , Ptr <git_submodule> -> CInt -> IO (CInt)+#ccall git_submodule_init , Ptr <git_submodule> -> CInt -> IO (CInt)+#ccall git_submodule_sync , Ptr <git_submodule> -> IO (CInt)+#ccall git_submodule_open , Ptr (Ptr <git_repository>) -> Ptr <git_submodule> -> IO (CInt)+#ccall git_submodule_reload , Ptr <git_submodule> -> IO (CInt)+#ccall git_submodule_reload_all , Ptr <git_repository> -> IO (CInt)+#ccall git_submodule_status , Ptr CUInt -> Ptr <git_submodule> -> IO (CInt)+#ccall git_submodule_location , Ptr CUInt -> Ptr <git_submodule> -> IO (CInt)
Bindings/Libgit2/Tag.hsc view
@@ -1,19 +1,22 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Tag where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common import Bindings.Libgit2.Types import Bindings.Libgit2.Oid import Bindings.Libgit2.Object+import Bindings.Libgit2.Strarray #cinline git_tag_lookup , Ptr (Ptr <git_tag>) -> Ptr <git_repository> -> Ptr <git_oid> -> IO (CInt)-#cinline git_tag_lookup_prefix , Ptr (Ptr <git_tag>) -> Ptr <git_repository> -> Ptr <git_oid> -> CUInt -> IO (CInt)+#cinline git_tag_lookup_prefix , Ptr (Ptr <git_tag>) -> Ptr <git_repository> -> Ptr <git_oid> -> CSize -> IO (CInt) #cinline git_tag_free , Ptr <git_tag> -> IO () #ccall git_tag_id , Ptr <git_tag> -> IO (Ptr <git_oid>) #ccall git_tag_target , Ptr (Ptr <git_object>) -> Ptr <git_tag> -> IO (CInt)-#ccall git_tag_target_oid , Ptr <git_tag> -> IO (Ptr <git_oid>)-#ccall git_tag_type , Ptr <git_tag> -> IO (<git_otype>)+#ccall git_tag_target_id , Ptr <git_tag> -> IO (Ptr <git_oid>)+#ccall git_tag_target_type , Ptr <git_tag> -> IO (<git_otype>) #ccall git_tag_name , Ptr <git_tag> -> IO (CString) #ccall git_tag_tagger , Ptr <git_tag> -> IO (Ptr <git_signature>) #ccall git_tag_message , Ptr <git_tag> -> IO (CString)@@ -23,4 +26,9 @@ #ccall git_tag_delete , Ptr <git_repository> -> CString -> IO (CInt) #ccall git_tag_list , Ptr <git_strarray> -> Ptr <git_repository> -> IO (CInt) #ccall git_tag_list_match , Ptr <git_strarray> -> CString -> Ptr <git_repository> -> IO (CInt)+{- typedef int (* git_tag_foreach_cb)(const char * name,+ git_oid * oid,+ void * payload); -}+#callback git_tag_foreach_cb , CString -> Ptr (<git_oid>) -> Ptr () -> IO CInt+#ccall git_tag_foreach , Ptr <git_repository> -> <git_tag_foreach_cb> -> Ptr () -> IO (CInt) #ccall git_tag_peel , Ptr (Ptr <git_object>) -> Ptr <git_tag> -> IO (CInt)
Bindings/Libgit2/Tag.hsc.helper.c view
@@ -2,5 +2,5 @@ #include <git2.h> BC_INLINE3(git_tag_lookup, git_tag**, git_repository*, const git_oid*, int)-BC_INLINE4(git_tag_lookup_prefix, git_tag**, git_repository*, const git_oid*, unsigned int, int)+BC_INLINE4(git_tag_lookup_prefix, git_tag**, git_repository*, const git_oid*, size_t, int) BC_INLINE1VOID(git_tag_free, git_tag*)
Bindings/Libgit2/Threads.hsc view
@@ -1,8 +1,10 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Threads where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common-#ccall git_threads_init , IO ()+#ccall git_threads_init , IO (CInt) #ccall git_threads_shutdown , IO ()
+ Bindings/Libgit2/Trace.hsc view
@@ -0,0 +1,31 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/trace.h>+module Bindings.Libgit2.Trace where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Common+import Bindings.Libgit2.Types+{- typedef enum {+ GIT_TRACE_NONE = 0,+ GIT_TRACE_FATAL = 1,+ GIT_TRACE_ERROR = 2,+ GIT_TRACE_WARN = 3,+ GIT_TRACE_INFO = 4,+ GIT_TRACE_DEBUG = 5,+ GIT_TRACE_TRACE = 6+ } git_trace_level_t; -}+#integral_t git_trace_level_t+#num GIT_TRACE_NONE+#num GIT_TRACE_FATAL+#num GIT_TRACE_ERROR+#num GIT_TRACE_WARN+#num GIT_TRACE_INFO+#num GIT_TRACE_DEBUG+#num GIT_TRACE_TRACE+{- typedef void (* git_trace_callback)(git_trace_level_t level,+ const char * msg); -}+#callback git_trace_callback , <git_trace_level_t> -> CString -> IO ()+#ccall git_trace_set , <git_trace_level_t> -> <git_trace_callback> -> IO (CInt)
+ Bindings/Libgit2/Transport.hsc view
@@ -0,0 +1,174 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-}+#include <bindings.dsl.h>+#include <git2.h>+#include <git2/transport.h>+module Bindings.Libgit2.Transport where+import Foreign.Ptr+#strict_import++import Bindings.Libgit2.Indexer+import Bindings.Libgit2.Net+import Bindings.Libgit2.Types+{- typedef enum {+ GIT_CREDTYPE_USERPASS_PLAINTEXT = 1+ } git_credtype_t; -}+#integral_t git_credtype_t+#num GIT_CREDTYPE_USERPASS_PLAINTEXT+{- typedef struct git_cred {+ git_credtype_t credtype; void (* free)(struct git_cred * cred);+ } git_cred; -}+#callback git_cred_free_callback , Ptr <git_cred> -> IO ()+#starttype git_cred+#field credtype , <git_credtype_t>+#field free , <git_cred_free_callback>+#stoptype+{- typedef struct git_cred_userpass_plaintext {+ git_cred parent; char * username; char * password;+ } git_cred_userpass_plaintext; -}+#starttype git_cred_userpass_plaintext+#field parent , <git_cred>+#field username , CString+#field password , CString+#stoptype+#ccall git_cred_userpass_plaintext_new , Ptr (Ptr <git_cred>) -> CString -> CString -> IO (CInt)+{- typedef int (* git_cred_acquire_cb)(git_cred * * cred,+ const char * url,+ const char * username_from_url,+ unsigned int allowed_types,+ void * payload); -}+#callback git_cred_acquire_cb , Ptr (Ptr (<git_cred>)) -> CString -> CString -> CUInt -> Ptr () -> IO CInt+{- typedef enum {+ GIT_TRANSPORTFLAGS_NONE = 0, GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1+ } git_transport_flags_t; -}+#integral_t git_transport_flags_t+#num GIT_TRANSPORTFLAGS_NONE+#num GIT_TRANSPORTFLAGS_NO_CHECK_CERT+{- typedef void (* git_transport_message_cb)(const char * str,+ int len,+ void * data); -}+#callback git_transport_message_cb , CString -> CInt -> Ptr () -> IO ()+{- typedef struct git_transport {+ unsigned int version;+ int (* set_callbacks)(struct git_transport * transport,+ git_transport_message_cb progress_cb,+ git_transport_message_cb error_cb,+ void * payload);+ int (* connect)(struct git_transport * transport,+ const char * url,+ git_cred_acquire_cb cred_acquire_cb,+ void * cred_acquire_payload,+ int direction,+ int flags);+ int (* ls)(struct git_transport * transport,+ git_headlist_cb list_cb,+ void * payload);+ int (* push)(struct git_transport * transport, git_push * push);+ int (* negotiate_fetch)(struct git_transport * transport,+ git_repository * repo,+ const git_remote_head * const * refs,+ size_t count);+ int (* download_pack)(struct git_transport * transport,+ git_repository * repo,+ git_transfer_progress * stats,+ git_transfer_progress_callback progress_cb,+ void * progress_payload);+ int (* is_connected)(struct git_transport * transport);+ int (* read_flags)(struct git_transport * transport, int * flags);+ void (* cancel)(struct git_transport * transport);+ int (* close)(struct git_transport * transport);+ void (* free)(struct git_transport * transport);+ } git_transport; -}+#callback git_transport_set_callbacks_callback , Ptr <git_transport> -> <git_transport_message_cb> -> <git_transport_message_cb> -> Ptr () -> IO CInt+#callback git_transport_connect_callback , Ptr <git_transport> -> CString -> <git_cred_acquire_cb> -> Ptr () -> CInt -> CInt -> IO CInt+#callback git_transport_ls_callback , Ptr <git_transport> -> <git_headlist_cb> -> Ptr () -> IO CInt+#callback git_transport_push_callback , Ptr <git_transport> -> Ptr <git_push> -> IO CInt+#callback git_transport_negotiate_fetch_callback , Ptr <git_transport> -> Ptr <git_repository> -> Ptr (Ptr <git_remote_head>) -> CSize -> IO CInt+#callback git_transport_download_pack_callback , Ptr <git_transport> -> Ptr <git_repository> -> Ptr <git_transfer_progress> -> <git_transfer_progress_callback> -> Ptr () -> IO CInt+#callback git_transport_is_connected_callback , Ptr <git_transport> -> IO CInt+#callback git_transport_read_flags_callback , Ptr <git_transport> -> Ptr CInt -> IO CInt+#callback git_transport_cancel_callback , Ptr <git_transport> -> IO ()+#callback git_transport_close_callback , Ptr <git_transport> -> IO CInt+#callback git_transport_free_callback , Ptr <git_transport> -> IO ()+#starttype git_transport+#field version , CUInt+#field set_callbacks , <git_transport_set_callbacks_callback>+#field connect , <git_transport_connect_callback>+#field ls , <git_transport_ls_callback>+#field push , <git_transport_push_callback>+#field negotiate_fetch , <git_transport_negotiate_fetch_callback>+#field download_pack , <git_transport_download_pack_callback>+#field is_connected , <git_transport_is_connected_callback>+#field read_flags , <git_transport_read_flags_callback>+#field cancel , <git_transport_cancel_callback>+#field close , <git_transport_close_callback>+#field free , <git_transport_free_callback>+#stoptype+#ccall git_transport_new , Ptr (Ptr <git_transport>) -> Ptr <git_remote> -> CString -> IO (CInt)+{- typedef int (* git_transport_cb)(git_transport * * out,+ git_remote * owner,+ void * param); -}+#callback git_transport_cb , Ptr (Ptr (<git_transport>)) -> Ptr (<git_remote>) -> Ptr () -> IO CInt+#ccall git_transport_dummy , Ptr (Ptr <git_transport>) -> Ptr <git_remote> -> Ptr () -> IO (CInt)+#ccall git_transport_local , Ptr (Ptr <git_transport>) -> Ptr <git_remote> -> Ptr () -> IO (CInt)+#ccall git_transport_smart , Ptr (Ptr <git_transport>) -> Ptr <git_remote> -> Ptr () -> IO (CInt)+{- typedef enum {+ GIT_SERVICE_UPLOADPACK_LS = 1,+ GIT_SERVICE_UPLOADPACK = 2,+ GIT_SERVICE_RECEIVEPACK_LS = 3,+ GIT_SERVICE_RECEIVEPACK = 4+ } git_smart_service_t; -}+#integral_t git_smart_service_t+#num GIT_SERVICE_UPLOADPACK_LS+#num GIT_SERVICE_UPLOADPACK+#num GIT_SERVICE_RECEIVEPACK_LS+#num GIT_SERVICE_RECEIVEPACK+{- struct git_smart_subtransport; -}+{- #opaque_t git_smart_subtransport -}+{- typedef struct git_smart_subtransport_stream {+ struct git_smart_subtransport * subtransport;+ int (* read)(struct git_smart_subtransport_stream * stream,+ char * buffer,+ size_t buf_size,+ size_t * bytes_read);+ int (* write)(struct git_smart_subtransport_stream * stream,+ const char * buffer,+ size_t len);+ void (* free)(struct git_smart_subtransport_stream * stream);+ } git_smart_subtransport_stream; -}+#callback git_smart_subtransport_stream_read_callback , Ptr <git_smart_subtransport_stream> -> CString -> CSize -> Ptr CSize -> IO CInt+#callback git_smart_subtransport_stream_write_callback , Ptr <git_smart_subtransport_stream> -> CString -> CSize -> IO CInt+#callback git_smart_subtransport_stream_free_callback , Ptr <git_smart_subtransport_stream> -> IO ()+#starttype git_smart_subtransport_stream+#field subtransport , Ptr <git_smart_subtransport>+#field read , <git_smart_subtransport_stream_read_callback>+#field write , <git_smart_subtransport_stream_write_callback>+#field free , <git_smart_subtransport_stream_free_callback>+#stoptype+{- typedef struct git_smart_subtransport {+ int (* action)(git_smart_subtransport_stream * * out,+ struct git_smart_subtransport * transport,+ const char * url,+ git_smart_service_t action);+ int (* close)(struct git_smart_subtransport * transport);+ void (* free)(struct git_smart_subtransport * transport);+ } git_smart_subtransport; -}+#callback git_smart_subtransport_action_callback , Ptr (Ptr <git_smart_subtransport_stream>) -> Ptr <git_smart_subtransport> -> CString -> <git_smart_service_t> -> IO CInt+#callback git_smart_subtransport_close_callback , Ptr <git_smart_subtransport> -> IO CInt+#callback git_smart_subtransport_free_callback , Ptr <git_smart_subtransport> -> IO ()+#starttype git_smart_subtransport+#field action , <git_smart_subtransport_action_callback>+#field close , <git_smart_subtransport_close_callback>+#field free , <git_smart_subtransport_free_callback>+#stoptype+{- typedef int (* git_smart_subtransport_cb)(git_smart_subtransport * * out,+ git_transport * owner); -}+#callback git_smart_subtransport_cb , Ptr (Ptr (<git_smart_subtransport>)) -> Ptr (<git_transport>) -> IO CInt+{- typedef struct git_smart_subtransport_definition {+ git_smart_subtransport_cb callback; unsigned rpc;+ } git_smart_subtransport_definition; -}+#starttype git_smart_subtransport_definition+#field callback , <git_smart_subtransport_cb>+#field rpc , CUInt+#stoptype+#ccall git_smart_subtransport_http , Ptr (Ptr <git_smart_subtransport>) -> Ptr <git_transport> -> IO (CInt)+#ccall git_smart_subtransport_git , Ptr (Ptr <git_smart_subtransport>) -> Ptr <git_transport> -> IO (CInt)
Bindings/Libgit2/Tree.hsc view
@@ -1,7 +1,9 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> #include <git2/tree.h> module Bindings.Libgit2.Tree where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common@@ -9,36 +11,43 @@ import Bindings.Libgit2.Oid import Bindings.Libgit2.Object #cinline git_tree_lookup , Ptr (Ptr <git_tree>) -> Ptr <git_repository> -> Ptr <git_oid> -> IO (CInt)-#cinline git_tree_lookup_prefix , Ptr (Ptr <git_tree>) -> Ptr <git_repository> -> Ptr <git_oid> -> CUInt -> IO (CInt)+#cinline git_tree_lookup_prefix , Ptr (Ptr <git_tree>) -> Ptr <git_repository> -> Ptr <git_oid> -> CSize -> IO (CInt) #cinline git_tree_free , Ptr <git_tree> -> IO () #ccall git_tree_id , Ptr <git_tree> -> IO (Ptr <git_oid>)-#ccall git_tree_entrycount , Ptr <git_tree> -> IO (CUInt)-#ccall git_treebuilder_entrycount , Ptr <git_treebuilder> -> IO (CUInt)+#ccall git_tree_owner , Ptr <git_tree> -> IO (Ptr <git_repository>)+#ccall git_tree_entrycount , Ptr <git_tree> -> IO (CSize) #ccall git_tree_entry_byname , Ptr <git_tree> -> CString -> IO (Ptr <git_tree_entry>)-#ccall git_tree_entry_byindex , Ptr <git_tree> -> CUInt -> IO (Ptr <git_tree_entry>)-#ccall git_tree_entry_attributes , Ptr <git_tree_entry> -> IO (CUInt)+#ccall git_tree_entry_byindex , Ptr <git_tree> -> CSize -> IO (Ptr <git_tree_entry>)+#ccall git_tree_entry_byoid , Ptr <git_tree> -> Ptr <git_oid> -> IO (Ptr <git_tree_entry>)+#ccall git_tree_entry_bypath , Ptr (Ptr <git_tree_entry>) -> Ptr <git_tree> -> CString -> IO (CInt)+#ccall git_tree_entry_dup , Ptr <git_tree_entry> -> IO (Ptr <git_tree_entry>)+#ccall git_tree_entry_free , Ptr <git_tree_entry> -> IO () #ccall git_tree_entry_name , Ptr <git_tree_entry> -> IO (CString) #ccall git_tree_entry_id , Ptr <git_tree_entry> -> IO (Ptr <git_oid>) #ccall git_tree_entry_type , Ptr <git_tree_entry> -> IO (<git_otype>)+#ccall git_tree_entry_filemode , Ptr <git_tree_entry> -> IO (<git_filemode_t>)+#ccall git_tree_entry_cmp , Ptr <git_tree_entry> -> Ptr <git_tree_entry> -> IO (CInt) #ccall git_tree_entry_to_object , Ptr (Ptr <git_object>) -> Ptr <git_repository> -> Ptr <git_tree_entry> -> IO (CInt)-#ccall git_tree_create_fromindex , Ptr <git_oid> -> Ptr <git_index> -> IO (CInt) #ccall git_treebuilder_create , Ptr (Ptr <git_treebuilder>) -> Ptr <git_tree> -> IO (CInt) #ccall git_treebuilder_clear , Ptr <git_treebuilder> -> IO ()+#ccall git_treebuilder_entrycount , Ptr <git_treebuilder> -> IO (CUInt) #ccall git_treebuilder_free , Ptr <git_treebuilder> -> IO () #ccall git_treebuilder_get , Ptr <git_treebuilder> -> CString -> IO (Ptr <git_tree_entry>)-#ccall git_treebuilder_insert , Ptr (Ptr <git_tree_entry>) -> Ptr <git_treebuilder> -> CString -> Ptr <git_oid> -> CUInt -> IO (CInt)+#ccall git_treebuilder_insert , Ptr (Ptr <git_tree_entry>) -> Ptr <git_treebuilder> -> CString -> Ptr <git_oid> -> <git_filemode_t> -> IO (CInt) #ccall git_treebuilder_remove , Ptr <git_treebuilder> -> CString -> IO (CInt)-#callback git_treebuilder_filter_callback , Ptr <git_tree_entry> -> Ptr () -> IO CInt-#ccall git_treebuilder_filter , Ptr <git_treebuilder> -> <git_treebuilder_filter_callback> -> Ptr () -> IO ()+{- typedef int (* git_treebuilder_filter_cb)(const git_tree_entry * entry,+ void * payload); -}+#callback git_treebuilder_filter_cb , Ptr (<git_tree_entry>) -> Ptr () -> IO CInt+#ccall git_treebuilder_filter , Ptr <git_treebuilder> -> <git_treebuilder_filter_cb> -> Ptr () -> IO () #ccall git_treebuilder_write , Ptr <git_oid> -> Ptr <git_repository> -> Ptr <git_treebuilder> -> IO (CInt)-#ccall git_tree_get_subtree , Ptr (Ptr <git_tree>) -> Ptr <git_tree> -> CString -> IO (CInt) {- typedef int (* git_treewalk_cb)(const char * root,- git_tree_entry * entry,+ const git_tree_entry * entry, void * payload); -}-#synonym_t git_treewalk_cb , CInt-{- enum git_treewalk_mode {- GIT_TREEWALK_PRE = 0, GIT_TREEWALK_POST = 1-}; -}+#callback git_treewalk_cb , CString -> Ptr (<git_tree_entry>) -> Ptr () -> IO CInt+{- typedef enum {+ GIT_TREEWALK_PRE = 0, GIT_TREEWALK_POST = 1+ } git_treewalk_mode; -}+#integral_t git_treewalk_mode #num GIT_TREEWALK_PRE #num GIT_TREEWALK_POST-#ccall git_tree_walk , Ptr <git_tree> -> CInt -> CInt -> Ptr () -> IO (CInt)+#ccall git_tree_walk , Ptr <git_tree> -> <git_treewalk_mode> -> <git_treewalk_cb> -> Ptr () -> IO (CInt)
Bindings/Libgit2/Tree.hsc.helper.c view
@@ -2,5 +2,5 @@ #include <git2.h> BC_INLINE3(git_tree_lookup, git_tree**, git_repository*, const git_oid*, int)-BC_INLINE4(git_tree_lookup_prefix, git_tree**, git_repository*, const git_oid*, unsigned int, int)+BC_INLINE4(git_tree_lookup_prefix, git_tree**, git_repository*, const git_oid*, size_t, int) BC_INLINE1VOID(git_tree_free, git_tree*)
Bindings/Libgit2/Types.hsc view
@@ -1,6 +1,8 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Types where+import Foreign.Ptr #strict_import import Bindings.Libgit2.Common@@ -33,12 +35,18 @@ #num GIT_OBJ_REF_DELTA {- typedef struct git_odb git_odb; -} #opaque_t git_odb--- {- typedef struct git_odb_backend git_odb_backend; -}--- #opaque_t git_odb_backend+{- typedef struct git_odb_backend git_odb_backend; -}+{- #opaque_t git_odb_backend -} {- typedef struct git_odb_object git_odb_object; -} #opaque_t git_odb_object--- {- typedef struct git_odb_stream git_odb_stream; -}--- #opaque_t git_odb_stream+{- typedef struct git_odb_stream git_odb_stream; -}+{- #opaque_t git_odb_stream -}+{- typedef struct git_odb_writepack git_odb_writepack; -}+{- #opaque_t git_odb_writepack -}+{- typedef struct git_refdb git_refdb; -}+#opaque_t git_refdb+{- typedef struct git_refdb_backend git_refdb_backend; -}+{- #opaque_t git_refdb_backend -} {- typedef struct git_repository git_repository; -} #opaque_t git_repository {- typedef struct git_object git_object; -}@@ -61,14 +69,16 @@ #opaque_t git_index {- typedef struct git_config git_config; -} #opaque_t git_config--- {- typedef struct git_config_file git_config_file; -}--- #opaque_t git_config_file+{- typedef struct git_config_backend git_config_backend; -}+{- #opaque_t git_config_backend -} {- typedef struct git_reflog_entry git_reflog_entry; -} #opaque_t git_reflog_entry {- typedef struct git_reflog git_reflog; -} #opaque_t git_reflog {- typedef struct git_note git_note; -} #opaque_t git_note+{- typedef struct git_packbuilder git_packbuilder; -}+#opaque_t git_packbuilder {- typedef struct git_time { git_time_t time; int offset; } git_time; -}@@ -90,16 +100,12 @@ GIT_REF_INVALID = 0, GIT_REF_OID = 1, GIT_REF_SYMBOLIC = 2,- GIT_REF_PACKED = 4,- GIT_REF_HAS_PEEL = 8,- GIT_REF_LISTALL = GIT_REF_OID | GIT_REF_SYMBOLIC | GIT_REF_PACKED+ GIT_REF_LISTALL = GIT_REF_OID | GIT_REF_SYMBOLIC } git_ref_t; -} #integral_t git_ref_t #num GIT_REF_INVALID #num GIT_REF_OID #num GIT_REF_SYMBOLIC-#num GIT_REF_PACKED-#num GIT_REF_HAS_PEEL #num GIT_REF_LISTALL {- typedef enum { GIT_BRANCH_LOCAL = 1, GIT_BRANCH_REMOTE = 2@@ -107,9 +113,28 @@ #integral_t git_branch_t #num GIT_BRANCH_LOCAL #num GIT_BRANCH_REMOTE+{- typedef enum {+ GIT_FILEMODE_NEW = 00,+ GIT_FILEMODE_TREE = 040000,+ GIT_FILEMODE_BLOB = 0100644,+ GIT_FILEMODE_BLOB_EXECUTABLE = 0100755,+ GIT_FILEMODE_LINK = 0120000,+ GIT_FILEMODE_COMMIT = 0160000+ } git_filemode_t; -}+#integral_t git_filemode_t+#num GIT_FILEMODE_NEW+#num GIT_FILEMODE_TREE+#num GIT_FILEMODE_BLOB+#num GIT_FILEMODE_BLOB_EXECUTABLE+#num GIT_FILEMODE_LINK+#num GIT_FILEMODE_COMMIT {- typedef struct git_refspec git_refspec; -} #opaque_t git_refspec {- typedef struct git_remote git_remote; -} #opaque_t git_remote--- {- typedef struct git_remote_head git_remote_head; -}--- #opaque_t git_remote_head+{- typedef struct git_push git_push; -}+#opaque_t git_push+{- typedef struct git_remote_head git_remote_head; -}+{- #opaque_t git_remote_head -}+{- typedef struct git_remote_callbacks git_remote_callbacks; -}+{- #opaque_t git_remote_callbacks -}
Bindings/Libgit2/Version.hsc view
@@ -1,5 +1,7 @@+{-# OPTIONS_GHC -fno-warn-unused-imports #-} #include <bindings.dsl.h> #include <git2.h> module Bindings.Libgit2.Version where+import Foreign.Ptr #strict_import
hlibgit2.cabal view
@@ -1,7 +1,7 @@ Name: hlibgit2-Version: 0.17.0.8+Version: 0.18.0.1 Synopsis: Low-level bindings to libgit2-Description: Bindings to libgit2 v0.17.0.+Description: Bindings to libgit2 v0.18.0. License-file: LICENSE License: MIT Author: John Wiegley, Sakari Jokinen, Jacob Stanleyyeah,@@ -15,7 +15,6 @@ libgit2/include/git2.h libgit2/include/git2/*.h libgit2/src/*.h- libgit2/src/compat/*.h libgit2/src/unix/*.h libgit2/src/win32/*.h libgit2/src/xdiff/*.h@@ -49,31 +48,47 @@ Bindings.Libgit2.Attr Bindings.Libgit2.Blob Bindings.Libgit2.Branch+ Bindings.Libgit2.Checkout+ Bindings.Libgit2.Clone Bindings.Libgit2.Commit Bindings.Libgit2.Common Bindings.Libgit2.Config+ Bindings.Libgit2.CredHelpers Bindings.Libgit2.Diff Bindings.Libgit2.Errors+ Bindings.Libgit2.Graph+ Bindings.Libgit2.Ignore Bindings.Libgit2.Index Bindings.Libgit2.Indexer Bindings.Libgit2.Merge+ Bindings.Libgit2.Message Bindings.Libgit2.Net Bindings.Libgit2.Notes Bindings.Libgit2.Object Bindings.Libgit2.Odb Bindings.Libgit2.OdbBackend Bindings.Libgit2.Oid+ Bindings.Libgit2.Pack+ Bindings.Libgit2.Push+ Bindings.Libgit2.Refdb+ Bindings.Libgit2.RefdbBackend Bindings.Libgit2.Reflog Bindings.Libgit2.Refs Bindings.Libgit2.Refspec Bindings.Libgit2.Remote Bindings.Libgit2.Repository+ Bindings.Libgit2.Reset+ Bindings.Libgit2.Revparse Bindings.Libgit2.Revwalk Bindings.Libgit2.Signature+ Bindings.Libgit2.Stash Bindings.Libgit2.Status+ Bindings.Libgit2.Strarray Bindings.Libgit2.Submodule Bindings.Libgit2.Tag Bindings.Libgit2.Threads+ Bindings.Libgit2.Trace+ Bindings.Libgit2.Transport Bindings.Libgit2.Tree Bindings.Libgit2.Types Bindings.Libgit2.Version@@ -84,33 +99,47 @@ Bindings/Libgit2/Blob.hsc.helper.c Bindings/Libgit2/Commit.hsc.helper.c Bindings/Libgit2/Common.hsc.helper.c+ Bindings/Libgit2/Oid.hsc.helper.c Bindings/Libgit2/Tag.hsc.helper.c Bindings/Libgit2/Tree.hsc.helper.c libgit2/src/attr.c libgit2/src/attr_file.c libgit2/src/blob.c libgit2/src/branch.c+ libgit2/src/buf_text.c libgit2/src/buffer.c libgit2/src/cache.c+ libgit2/src/checkout.c+ libgit2/src/clone.c libgit2/src/commit.c+ libgit2/src/commit_list.c+ libgit2/src/compress.c libgit2/src/config.c libgit2/src/config_cache.c libgit2/src/config_file.c libgit2/src/crlf.c+ libgit2/src/date.c libgit2/src/delta-apply.c+ libgit2/src/delta.c libgit2/src/diff.c libgit2/src/diff_output.c+ libgit2/src/diff_tform.c libgit2/src/errors.c libgit2/src/fetch.c+ libgit2/src/fetchhead.c libgit2/src/filebuf.c libgit2/src/fileops.c libgit2/src/filter.c+ libgit2/src/fnmatch.c libgit2/src/global.c+ libgit2/src/graph.c libgit2/src/hash.c+ libgit2/src/hashsig.c libgit2/src/ignore.c libgit2/src/index.c libgit2/src/indexer.c libgit2/src/iterator.c+ libgit2/src/merge.c libgit2/src/message.c libgit2/src/mwindow.c libgit2/src/netops.c@@ -120,35 +149,46 @@ libgit2/src/odb_loose.c libgit2/src/odb_pack.c libgit2/src/oid.c+ libgit2/src/pack-objects.c libgit2/src/pack.c libgit2/src/path.c- libgit2/src/pkt.c+ libgit2/src/pathspec.c libgit2/src/pool.c libgit2/src/posix.c libgit2/src/pqueue.c- libgit2/src/protocol.c+ libgit2/src/push.c+ libgit2/src/refdb.c+ libgit2/src/refdb_fs.c libgit2/src/reflog.c libgit2/src/refs.c libgit2/src/refspec.c libgit2/src/remote.c libgit2/src/repository.c+ libgit2/src/reset.c+ libgit2/src/revparse.c libgit2/src/revwalk.c- libgit2/src/sha1.c libgit2/src/sha1_lookup.c libgit2/src/signature.c+ libgit2/src/stash.c libgit2/src/status.c libgit2/src/submodule.c libgit2/src/tag.c libgit2/src/thread-utils.c+ libgit2/src/trace.c libgit2/src/transport.c libgit2/src/tree-cache.c libgit2/src/tree.c libgit2/src/tsort.c libgit2/src/util.c libgit2/src/vector.c+ libgit2/src/transports/cred.c+ libgit2/src/transports/cred_helpers.c libgit2/src/transports/git.c libgit2/src/transports/http.c libgit2/src/transports/local.c+ libgit2/src/transports/smart.c+ libgit2/src/transports/smart_pkt.c+ libgit2/src/transports/smart_protocol.c libgit2/src/xdiff/xdiffi.c libgit2/src/xdiff/xemit.c libgit2/src/xdiff/xhistogram.c@@ -163,21 +203,30 @@ libgit2/src libgit2/deps/http-parser - cc-options: -g -DGIT_THREADS -D_FILE_OFFSET_BITS=64+ cc-options: -g -DGIT_THREADS -D_FILE_OFFSET_BITS=64 -DGIT_SSL ld-options: -g if os(windows)- cc-options: -DGIT_WIN32 -DWIN32 -D_DEBUG -D_WIN32_WINNT=0x0501+ cc-options: -DGIT_WIN32 -DWIN32 -DWIN32_SHA1 -D_DEBUG -D_WIN32_WINNT=0x0501 -DGIT_WINHTTP c-sources:+ libgit2/src/hash/hash_win32.c+ libgit2/src/transports/winhttp.c libgit2/src/win32/dir.c- libgit2/src/win32/pthread.c- libgit2/src/win32/utf-conv.c+ libgit2/src/win32/error.c+ libgit2/src/win32/findfile.c libgit2/src/win32/map.c libgit2/src/win32/posix_w32.c+ libgit2/src/win32/precompiled.c+ libgit2/src/win32/pthread.c+ libgit2/src/win32/utf-conv.c include-dirs: libgit2/src/win32 else- cc-options: -D_GNU_SOURCE+ cc-options: -D_GNU_SOURCE -DOPENSSL_SHA1 -Wno-deprecated-declarations c-sources:+ -- libgit2/src/hash/hash_generic.c libgit2/src/unix/map.c+ libgit2/src/unix/realpath.c include-dirs: libgit2/src/unix+ extra-libraries:+ ssl, crypto
libgit2/deps/http-parser/http_parser.c view
@@ -21,1758 +21,2154 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */-#include <http_parser.h>-#include <assert.h>-#include <stddef.h>---#ifndef MIN-# define MIN(a,b) ((a) < (b) ? (a) : (b))-#endif---#if HTTP_PARSER_DEBUG-#define SET_ERRNO(e) \-do { \- parser->http_errno = (e); \- parser->error_lineno = __LINE__; \-} while (0)-#else-#define SET_ERRNO(e) \-do { \- parser->http_errno = (e); \-} while(0)-#endif---#define CALLBACK2(FOR) \-do { \- if (settings->on_##FOR) { \- if (0 != settings->on_##FOR(parser)) { \- SET_ERRNO(HPE_CB_##FOR); \- return (p - data); \- } \- } \-} while (0)---#define MARK(FOR) \-do { \- FOR##_mark = p; \-} while (0)--#define CALLBACK(FOR) \-do { \- if (FOR##_mark) { \- if (settings->on_##FOR) { \- if (0 != settings->on_##FOR(parser, \- FOR##_mark, \- p - FOR##_mark)) \- { \- SET_ERRNO(HPE_CB_##FOR); \- return (p - data); \- } \- } \- FOR##_mark = NULL; \- } \-} while (0)---#define PROXY_CONNECTION "proxy-connection"-#define CONNECTION "connection"-#define CONTENT_LENGTH "content-length"-#define TRANSFER_ENCODING "transfer-encoding"-#define UPGRADE "upgrade"-#define CHUNKED "chunked"-#define KEEP_ALIVE "keep-alive"-#define CLOSE "close"---static const char *method_strings[] =- { "DELETE"- , "GET"- , "HEAD"- , "POST"- , "PUT"- , "CONNECT"- , "OPTIONS"- , "TRACE"- , "COPY"- , "LOCK"- , "MKCOL"- , "MOVE"- , "PROPFIND"- , "PROPPATCH"- , "UNLOCK"- , "REPORT"- , "MKACTIVITY"- , "CHECKOUT"- , "MERGE"- , "M-SEARCH"- , "NOTIFY"- , "SUBSCRIBE"- , "UNSUBSCRIBE"- , "PATCH"- };---/* Tokens as defined by rfc 2616. Also lowercases them.- * token = 1*<any CHAR except CTLs or separators>- * separators = "(" | ")" | "<" | ">" | "@"- * | "," | ";" | ":" | "\" | <">- * | "/" | "[" | "]" | "?" | "="- * | "{" | "}" | SP | HT- */-static const char tokens[256] = {-/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */- 0, 0, 0, 0, 0, 0, 0, 0,-/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */- 0, 0, 0, 0, 0, 0, 0, 0,-/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */- 0, 0, 0, 0, 0, 0, 0, 0,-/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */- 0, 0, 0, 0, 0, 0, 0, 0,-/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */- ' ', '!', '"', '#', '$', '%', '&', '\'',-/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */- 0, 0, '*', '+', 0, '-', '.', '/',-/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */- '0', '1', '2', '3', '4', '5', '6', '7',-/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */- '8', '9', 0, 0, 0, 0, 0, 0,-/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */- 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',-/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */- 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',-/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */- 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',-/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */- 'x', 'y', 'z', 0, 0, 0, '^', '_',-/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */- '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',-/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */- 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',-/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */- 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',-/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */- 'x', 'y', 'z', 0, '|', '}', '~', 0 };---static const int8_t unhex[256] =- {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1- ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1- ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1- , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1- ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1- ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1- ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1- ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1- };---static const uint8_t normal_url_char[256] = {-/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */- 0, 0, 0, 0, 0, 0, 0, 0,-/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */- 0, 0, 0, 0, 0, 0, 0, 0,-/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */- 0, 0, 0, 0, 0, 0, 0, 0,-/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */- 0, 0, 0, 0, 0, 0, 0, 0,-/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */- 0, 1, 1, 0, 1, 1, 1, 1,-/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */- 1, 1, 1, 1, 1, 1, 1, 0,-/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */- 1, 1, 1, 1, 1, 1, 1, 1,-/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */- 1, 1, 1, 1, 1, 1, 1, 0, };---enum state- { s_dead = 1 /* important that this is > 0 */-- , s_start_req_or_res- , s_res_or_resp_H- , s_start_res- , s_res_H- , s_res_HT- , s_res_HTT- , s_res_HTTP- , s_res_first_http_major- , s_res_http_major- , s_res_first_http_minor- , s_res_http_minor- , s_res_first_status_code- , s_res_status_code- , s_res_status- , s_res_line_almost_done-- , s_start_req-- , s_req_method- , s_req_spaces_before_url- , s_req_schema- , s_req_schema_slash- , s_req_schema_slash_slash- , s_req_host- , s_req_port- , s_req_path- , s_req_query_string_start- , s_req_query_string- , s_req_fragment_start- , s_req_fragment- , s_req_http_start- , s_req_http_H- , s_req_http_HT- , s_req_http_HTT- , s_req_http_HTTP- , s_req_first_http_major- , s_req_http_major- , s_req_first_http_minor- , s_req_http_minor- , s_req_line_almost_done-- , s_header_field_start- , s_header_field- , s_header_value_start- , s_header_value- , s_header_value_lws-- , s_header_almost_done-- , s_chunk_size_start- , s_chunk_size- , s_chunk_parameters- , s_chunk_size_almost_done- - , s_headers_almost_done- /* Important: 's_headers_almost_done' must be the last 'header' state. All- * states beyond this must be 'body' states. It is used for overflow- * checking. See the PARSING_HEADER() macro.- */-- , s_chunk_data- , s_chunk_data_almost_done- , s_chunk_data_done-- , s_body_identity- , s_body_identity_eof- };---#define PARSING_HEADER(state) (state <= s_headers_almost_done)---enum header_states- { h_general = 0- , h_C- , h_CO- , h_CON-- , h_matching_connection- , h_matching_proxy_connection- , h_matching_content_length- , h_matching_transfer_encoding- , h_matching_upgrade-- , h_connection- , h_content_length- , h_transfer_encoding- , h_upgrade-- , h_matching_transfer_encoding_chunked- , h_matching_connection_keep_alive- , h_matching_connection_close-- , h_transfer_encoding_chunked- , h_connection_keep_alive- , h_connection_close- };---/* Macros for character classes; depends on strict-mode */-#define CR '\r'-#define LF '\n'-#define LOWER(c) (unsigned char)(c | 0x20)-#define TOKEN(c) (tokens[(unsigned char)c])-#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z')-#define IS_NUM(c) ((c) >= '0' && (c) <= '9')-#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))--#if HTTP_PARSER_STRICT-#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)])-#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')-#else-#define IS_URL_CHAR(c) \- (normal_url_char[(unsigned char) (c)] || ((c) & 0x80))-#define IS_HOST_CHAR(c) \- (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')-#endif---#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)---#if HTTP_PARSER_STRICT-# define STRICT_CHECK(cond) \-do { \- if (cond) { \- SET_ERRNO(HPE_STRICT); \- goto error; \- } \-} while (0)-# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)-#else-# define STRICT_CHECK(cond)-# define NEW_MESSAGE() start_state-#endif---/* Map errno values to strings for human-readable output */-#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },-static struct {- const char *name;- const char *description;-} http_strerror_tab[] = {- HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)-};-#undef HTTP_STRERROR_GEN---size_t http_parser_execute (http_parser *parser,- const http_parser_settings *settings,- const char *data,- size_t len)-{- char c, ch;- int8_t unhex_val;- const char *p = data, *pe;- size_t to_read;- enum state state;- enum header_states header_state;- size_t index = parser->index;- size_t nread = parser->nread;- const char *header_field_mark, *header_value_mark, *url_mark;- const char *matcher;-- /* We're in an error state. Don't bother doing anything. */- if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {- return 0;- }-- state = (enum state) parser->state;- header_state = (enum header_states) parser->header_state;-- if (len == 0) {- switch (state) {- case s_body_identity_eof:- CALLBACK2(message_complete);- return 0;-- case s_dead:- case s_start_req_or_res:- case s_start_res:- case s_start_req:- return 0;-- default:- SET_ERRNO(HPE_INVALID_EOF_STATE);- return 1;- }- }-- /* technically we could combine all of these (except for url_mark) into one- variable, saving stack space, but it seems more clear to have them- separated. */- header_field_mark = 0;- header_value_mark = 0;- url_mark = 0;-- if (state == s_header_field)- header_field_mark = data;- if (state == s_header_value)- header_value_mark = data;- if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash- || state == s_req_schema_slash_slash || state == s_req_port- || state == s_req_query_string_start || state == s_req_query_string- || state == s_req_host- || state == s_req_fragment_start || state == s_req_fragment)- url_mark = data;-- for (p=data, pe=data+len; p != pe; p++) {- ch = *p;-- if (PARSING_HEADER(state)) {- ++nread;- /* Buffer overflow attack */- if (nread > HTTP_MAX_HEADER_SIZE) {- SET_ERRNO(HPE_HEADER_OVERFLOW);- goto error;- }- }-- switch (state) {-- case s_dead:- /* this state is used after a 'Connection: close' message- * the parser will error out if it reads another message- */- SET_ERRNO(HPE_CLOSED_CONNECTION);- goto error;-- case s_start_req_or_res:- {- if (ch == CR || ch == LF)- break;- parser->flags = 0;- parser->content_length = -1;-- CALLBACK2(message_begin);-- if (ch == 'H')- state = s_res_or_resp_H;- else {- parser->type = HTTP_REQUEST;- goto start_req_method_assign;- }- break;- }-- case s_res_or_resp_H:- if (ch == 'T') {- parser->type = HTTP_RESPONSE;- state = s_res_HT;- } else {- if (ch != 'E') {- SET_ERRNO(HPE_INVALID_CONSTANT);- goto error;- }-- parser->type = HTTP_REQUEST;- parser->method = HTTP_HEAD;- index = 2;- state = s_req_method;- }- break;-- case s_start_res:- {- parser->flags = 0;- parser->content_length = -1;-- CALLBACK2(message_begin);-- switch (ch) {- case 'H':- state = s_res_H;- break;-- case CR:- case LF:- break;-- default:- SET_ERRNO(HPE_INVALID_CONSTANT);- goto error;- }- break;- }-- case s_res_H:- STRICT_CHECK(ch != 'T');- state = s_res_HT;- break;-- case s_res_HT:- STRICT_CHECK(ch != 'T');- state = s_res_HTT;- break;-- case s_res_HTT:- STRICT_CHECK(ch != 'P');- state = s_res_HTTP;- break;-- case s_res_HTTP:- STRICT_CHECK(ch != '/');- state = s_res_first_http_major;- break;-- case s_res_first_http_major:- if (ch < '1' || ch > '9') {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- parser->http_major = ch - '0';- state = s_res_http_major;- break;-- /* major HTTP version or dot */- case s_res_http_major:- {- if (ch == '.') {- state = s_res_first_http_minor;- break;- }-- if (!IS_NUM(ch)) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- parser->http_major *= 10;- parser->http_major += ch - '0';-- if (parser->http_major > 999) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- break;- }-- /* first digit of minor HTTP version */- case s_res_first_http_minor:- if (!IS_NUM(ch)) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- parser->http_minor = ch - '0';- state = s_res_http_minor;- break;-- /* minor HTTP version or end of request line */- case s_res_http_minor:- {- if (ch == ' ') {- state = s_res_first_status_code;- break;- }-- if (!IS_NUM(ch)) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- parser->http_minor *= 10;- parser->http_minor += ch - '0';-- if (parser->http_minor > 999) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- break;- }-- case s_res_first_status_code:- {- if (!IS_NUM(ch)) {- if (ch == ' ') {- break;- }-- SET_ERRNO(HPE_INVALID_STATUS);- goto error;- }- parser->status_code = ch - '0';- state = s_res_status_code;- break;- }-- case s_res_status_code:- {- if (!IS_NUM(ch)) {- switch (ch) {- case ' ':- state = s_res_status;- break;- case CR:- state = s_res_line_almost_done;- break;- case LF:- state = s_header_field_start;- break;- default:- SET_ERRNO(HPE_INVALID_STATUS);- goto error;- }- break;- }-- parser->status_code *= 10;- parser->status_code += ch - '0';-- if (parser->status_code > 999) {- SET_ERRNO(HPE_INVALID_STATUS);- goto error;- }-- break;- }-- case s_res_status:- /* the human readable status. e.g. "NOT FOUND"- * we are not humans so just ignore this */- if (ch == CR) {- state = s_res_line_almost_done;- break;- }-- if (ch == LF) {- state = s_header_field_start;- break;- }- break;-- case s_res_line_almost_done:- STRICT_CHECK(ch != LF);- state = s_header_field_start;- break;-- case s_start_req:- {- if (ch == CR || ch == LF)- break;- parser->flags = 0;- parser->content_length = -1;-- CALLBACK2(message_begin);-- if (!IS_ALPHA(ch)) {- SET_ERRNO(HPE_INVALID_METHOD);- goto error;- }-- start_req_method_assign:- parser->method = (enum http_method) 0;- index = 1;- switch (ch) {- case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;- case 'D': parser->method = HTTP_DELETE; break;- case 'G': parser->method = HTTP_GET; break;- case 'H': parser->method = HTTP_HEAD; break;- case 'L': parser->method = HTTP_LOCK; break;- case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;- case 'N': parser->method = HTTP_NOTIFY; break;- case 'O': parser->method = HTTP_OPTIONS; break;- case 'P': parser->method = HTTP_POST;- /* or PROPFIND or PROPPATCH or PUT or PATCH */- break;- case 'R': parser->method = HTTP_REPORT; break;- case 'S': parser->method = HTTP_SUBSCRIBE; break;- case 'T': parser->method = HTTP_TRACE; break;- case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;- default:- SET_ERRNO(HPE_INVALID_METHOD);- goto error;- }- state = s_req_method;- break;- }-- case s_req_method:- {- if (ch == '\0') {- SET_ERRNO(HPE_INVALID_METHOD);- goto error;- }-- matcher = method_strings[parser->method];- if (ch == ' ' && matcher[index] == '\0') {- state = s_req_spaces_before_url;- } else if (ch == matcher[index]) {- ; /* nada */- } else if (parser->method == HTTP_CONNECT) {- if (index == 1 && ch == 'H') {- parser->method = HTTP_CHECKOUT;- } else if (index == 2 && ch == 'P') {- parser->method = HTTP_COPY;- } else {- goto error;- }- } else if (parser->method == HTTP_MKCOL) {- if (index == 1 && ch == 'O') {- parser->method = HTTP_MOVE;- } else if (index == 1 && ch == 'E') {- parser->method = HTTP_MERGE;- } else if (index == 1 && ch == '-') {- parser->method = HTTP_MSEARCH;- } else if (index == 2 && ch == 'A') {- parser->method = HTTP_MKACTIVITY;- } else {- goto error;- }- } else if (index == 1 && parser->method == HTTP_POST) {- if (ch == 'R') {- parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */- } else if (ch == 'U') {- parser->method = HTTP_PUT;- } else if (ch == 'A') {- parser->method = HTTP_PATCH;- } else {- goto error;- }- } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') {- parser->method = HTTP_UNSUBSCRIBE;- } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {- parser->method = HTTP_PROPPATCH;- } else {- SET_ERRNO(HPE_INVALID_METHOD);- goto error;- }-- ++index;- break;- }- case s_req_spaces_before_url:- {- if (ch == ' ') break;-- if (ch == '/' || ch == '*') {- MARK(url);- state = s_req_path;- break;- }-- /* Proxied requests are followed by scheme of an absolute URI (alpha).- * CONNECT is followed by a hostname, which begins with alphanum.- * All other methods are followed by '/' or '*' (handled above).- */- if (IS_ALPHA(ch) || (parser->method == HTTP_CONNECT && IS_NUM(ch))) {- MARK(url);- state = (parser->method == HTTP_CONNECT) ? s_req_host : s_req_schema;- break;- }-- SET_ERRNO(HPE_INVALID_URL);- goto error;- }-- case s_req_schema:- {- if (IS_ALPHA(ch)) break;-- if (ch == ':') {- state = s_req_schema_slash;- break;- }-- SET_ERRNO(HPE_INVALID_URL);- goto error;- }-- case s_req_schema_slash:- STRICT_CHECK(ch != '/');- state = s_req_schema_slash_slash;- break;-- case s_req_schema_slash_slash:- STRICT_CHECK(ch != '/');- state = s_req_host;- break;-- case s_req_host:- {- if (IS_HOST_CHAR(ch)) break;- switch (ch) {- case ':':- state = s_req_port;- break;- case '/':- state = s_req_path;- break;- case ' ':- /* The request line looks like:- * "GET http://foo.bar.com HTTP/1.1"- * That is, there is no path.- */- CALLBACK(url);- state = s_req_http_start;- break;- case '?':- state = s_req_query_string_start;- break;- default:- SET_ERRNO(HPE_INVALID_HOST);- goto error;- }- break;- }-- case s_req_port:- {- if (IS_NUM(ch)) break;- switch (ch) {- case '/':- state = s_req_path;- break;- case ' ':- /* The request line looks like:- * "GET http://foo.bar.com:1234 HTTP/1.1"- * That is, there is no path.- */- CALLBACK(url);- state = s_req_http_start;- break;- case '?':- state = s_req_query_string_start;- break;- default:- SET_ERRNO(HPE_INVALID_PORT);- goto error;- }- break;- }-- case s_req_path:- {- if (IS_URL_CHAR(ch)) break;-- switch (ch) {- case ' ':- CALLBACK(url);- state = s_req_http_start;- break;- case CR:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_req_line_almost_done;- break;- case LF:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_header_field_start;- break;- case '?':- state = s_req_query_string_start;- break;- case '#':- state = s_req_fragment_start;- break;- default:- SET_ERRNO(HPE_INVALID_PATH);- goto error;- }- break;- }-- case s_req_query_string_start:- {- if (IS_URL_CHAR(ch)) {- state = s_req_query_string;- break;- }-- switch (ch) {- case '?':- break; /* XXX ignore extra '?' ... is this right? */- case ' ':- CALLBACK(url);- state = s_req_http_start;- break;- case CR:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_req_line_almost_done;- break;- case LF:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_header_field_start;- break;- case '#':- state = s_req_fragment_start;- break;- default:- SET_ERRNO(HPE_INVALID_QUERY_STRING);- goto error;- }- break;- }-- case s_req_query_string:- {- if (IS_URL_CHAR(ch)) break;-- switch (ch) {- case '?':- /* allow extra '?' in query string */- break;- case ' ':- CALLBACK(url);- state = s_req_http_start;- break;- case CR:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_req_line_almost_done;- break;- case LF:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_header_field_start;- break;- case '#':- state = s_req_fragment_start;- break;- default:- SET_ERRNO(HPE_INVALID_QUERY_STRING);- goto error;- }- break;- }-- case s_req_fragment_start:- {- if (IS_URL_CHAR(ch)) {- state = s_req_fragment;- break;- }-- switch (ch) {- case ' ':- CALLBACK(url);- state = s_req_http_start;- break;- case CR:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_req_line_almost_done;- break;- case LF:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_header_field_start;- break;- case '?':- state = s_req_fragment;- break;- case '#':- break;- default:- SET_ERRNO(HPE_INVALID_FRAGMENT);- goto error;- }- break;- }-- case s_req_fragment:- {- if (IS_URL_CHAR(ch)) break;-- switch (ch) {- case ' ':- CALLBACK(url);- state = s_req_http_start;- break;- case CR:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_req_line_almost_done;- break;- case LF:- CALLBACK(url);- parser->http_major = 0;- parser->http_minor = 9;- state = s_header_field_start;- break;- case '?':- case '#':- break;- default:- SET_ERRNO(HPE_INVALID_FRAGMENT);- goto error;- }- break;- }-- case s_req_http_start:- switch (ch) {- case 'H':- state = s_req_http_H;- break;- case ' ':- break;- default:- SET_ERRNO(HPE_INVALID_CONSTANT);- goto error;- }- break;-- case s_req_http_H:- STRICT_CHECK(ch != 'T');- state = s_req_http_HT;- break;-- case s_req_http_HT:- STRICT_CHECK(ch != 'T');- state = s_req_http_HTT;- break;-- case s_req_http_HTT:- STRICT_CHECK(ch != 'P');- state = s_req_http_HTTP;- break;-- case s_req_http_HTTP:- STRICT_CHECK(ch != '/');- state = s_req_first_http_major;- break;-- /* first digit of major HTTP version */- case s_req_first_http_major:- if (ch < '1' || ch > '9') {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- parser->http_major = ch - '0';- state = s_req_http_major;- break;-- /* major HTTP version or dot */- case s_req_http_major:- {- if (ch == '.') {- state = s_req_first_http_minor;- break;- }-- if (!IS_NUM(ch)) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- parser->http_major *= 10;- parser->http_major += ch - '0';-- if (parser->http_major > 999) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- break;- }-- /* first digit of minor HTTP version */- case s_req_first_http_minor:- if (!IS_NUM(ch)) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- parser->http_minor = ch - '0';- state = s_req_http_minor;- break;-- /* minor HTTP version or end of request line */- case s_req_http_minor:- {- if (ch == CR) {- state = s_req_line_almost_done;- break;- }-- if (ch == LF) {- state = s_header_field_start;- break;- }-- /* XXX allow spaces after digit? */-- if (!IS_NUM(ch)) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- parser->http_minor *= 10;- parser->http_minor += ch - '0';-- if (parser->http_minor > 999) {- SET_ERRNO(HPE_INVALID_VERSION);- goto error;- }-- break;- }-- /* end of request line */- case s_req_line_almost_done:- {- if (ch != LF) {- SET_ERRNO(HPE_LF_EXPECTED);- goto error;- }-- state = s_header_field_start;- break;- }-- case s_header_field_start:- header_field_start:- {- if (ch == CR) {- state = s_headers_almost_done;- break;- }-- if (ch == LF) {- /* they might be just sending \n instead of \r\n so this would be- * the second \n to denote the end of headers*/- state = s_headers_almost_done;- goto headers_almost_done;- }-- c = TOKEN(ch);-- if (!c) {- SET_ERRNO(HPE_INVALID_HEADER_TOKEN);- goto error;- }-- MARK(header_field);-- index = 0;- state = s_header_field;-- switch (c) {- case 'c':- header_state = h_C;- break;-- case 'p':- header_state = h_matching_proxy_connection;- break;-- case 't':- header_state = h_matching_transfer_encoding;- break;-- case 'u':- header_state = h_matching_upgrade;- break;-- default:- header_state = h_general;- break;- }- break;- }-- case s_header_field:- {- c = TOKEN(ch);-- if (c) {- switch (header_state) {- case h_general:- break;-- case h_C:- index++;- header_state = (c == 'o' ? h_CO : h_general);- break;-- case h_CO:- index++;- header_state = (c == 'n' ? h_CON : h_general);- break;-- case h_CON:- index++;- switch (c) {- case 'n':- header_state = h_matching_connection;- break;- case 't':- header_state = h_matching_content_length;- break;- default:- header_state = h_general;- break;- }- break;-- /* connection */-- case h_matching_connection:- index++;- if (index > sizeof(CONNECTION)-1- || c != CONNECTION[index]) {- header_state = h_general;- } else if (index == sizeof(CONNECTION)-2) {- header_state = h_connection;- }- break;-- /* proxy-connection */-- case h_matching_proxy_connection:- index++;- if (index > sizeof(PROXY_CONNECTION)-1- || c != PROXY_CONNECTION[index]) {- header_state = h_general;- } else if (index == sizeof(PROXY_CONNECTION)-2) {- header_state = h_connection;- }- break;-- /* content-length */-- case h_matching_content_length:- index++;- if (index > sizeof(CONTENT_LENGTH)-1- || c != CONTENT_LENGTH[index]) {- header_state = h_general;- } else if (index == sizeof(CONTENT_LENGTH)-2) {- header_state = h_content_length;- }- break;-- /* transfer-encoding */-- case h_matching_transfer_encoding:- index++;- if (index > sizeof(TRANSFER_ENCODING)-1- || c != TRANSFER_ENCODING[index]) {- header_state = h_general;- } else if (index == sizeof(TRANSFER_ENCODING)-2) {- header_state = h_transfer_encoding;- }- break;-- /* upgrade */-- case h_matching_upgrade:- index++;- if (index > sizeof(UPGRADE)-1- || c != UPGRADE[index]) {- header_state = h_general;- } else if (index == sizeof(UPGRADE)-2) {- header_state = h_upgrade;- }- break;-- case h_connection:- case h_content_length:- case h_transfer_encoding:- case h_upgrade:- if (ch != ' ') header_state = h_general;- break;-- default:- assert(0 && "Unknown header_state");- break;- }- break;- }-- if (ch == ':') {- CALLBACK(header_field);- state = s_header_value_start;- break;- }-- if (ch == CR) {- state = s_header_almost_done;- CALLBACK(header_field);- break;- }-- if (ch == LF) {- CALLBACK(header_field);- state = s_header_field_start;- break;- }-- SET_ERRNO(HPE_INVALID_HEADER_TOKEN);- goto error;- }-- case s_header_value_start:- {- if (ch == ' ' || ch == '\t') break;-- MARK(header_value);-- state = s_header_value;- index = 0;-- if (ch == CR) {- CALLBACK(header_value);- header_state = h_general;- state = s_header_almost_done;- break;- }-- if (ch == LF) {- CALLBACK(header_value);- state = s_header_field_start;- break;- }-- c = LOWER(ch);-- switch (header_state) {- case h_upgrade:- parser->flags |= F_UPGRADE;- header_state = h_general;- break;-- case h_transfer_encoding:- /* looking for 'Transfer-Encoding: chunked' */- if ('c' == c) {- header_state = h_matching_transfer_encoding_chunked;- } else {- header_state = h_general;- }- break;-- case h_content_length:- if (!IS_NUM(ch)) {- SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);- goto error;- }-- parser->content_length = ch - '0';- break;-- case h_connection:- /* looking for 'Connection: keep-alive' */- if (c == 'k') {- header_state = h_matching_connection_keep_alive;- /* looking for 'Connection: close' */- } else if (c == 'c') {- header_state = h_matching_connection_close;- } else {- header_state = h_general;- }- break;-- default:- header_state = h_general;- break;- }- break;- }-- case s_header_value:- {-- if (ch == CR) {- CALLBACK(header_value);- state = s_header_almost_done;- break;- }-- if (ch == LF) {- CALLBACK(header_value);- goto header_almost_done;- }-- c = LOWER(ch);-- switch (header_state) {- case h_general:- break;-- case h_connection:- case h_transfer_encoding:- assert(0 && "Shouldn't get here.");- break;-- case h_content_length:- if (ch == ' ') break;- if (!IS_NUM(ch)) {- SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);- goto error;- }-- parser->content_length *= 10;- parser->content_length += ch - '0';- break;-- /* Transfer-Encoding: chunked */- case h_matching_transfer_encoding_chunked:- index++;- if (index > sizeof(CHUNKED)-1- || c != CHUNKED[index]) {- header_state = h_general;- } else if (index == sizeof(CHUNKED)-2) {- header_state = h_transfer_encoding_chunked;- }- break;-- /* looking for 'Connection: keep-alive' */- case h_matching_connection_keep_alive:- index++;- if (index > sizeof(KEEP_ALIVE)-1- || c != KEEP_ALIVE[index]) {- header_state = h_general;- } else if (index == sizeof(KEEP_ALIVE)-2) {- header_state = h_connection_keep_alive;- }- break;-- /* looking for 'Connection: close' */- case h_matching_connection_close:- index++;- if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) {- header_state = h_general;- } else if (index == sizeof(CLOSE)-2) {- header_state = h_connection_close;- }- break;-- case h_transfer_encoding_chunked:- case h_connection_keep_alive:- case h_connection_close:- if (ch != ' ') header_state = h_general;- break;-- default:- state = s_header_value;- header_state = h_general;- break;- }- break;- }-- case s_header_almost_done:- header_almost_done:- {- STRICT_CHECK(ch != LF);-- state = s_header_value_lws;-- switch (header_state) {- case h_connection_keep_alive:- parser->flags |= F_CONNECTION_KEEP_ALIVE;- break;- case h_connection_close:- parser->flags |= F_CONNECTION_CLOSE;- break;- case h_transfer_encoding_chunked:- parser->flags |= F_CHUNKED;- break;- default:- break;- }- break;- }-- case s_header_value_lws:- {- if (ch == ' ' || ch == '\t')- state = s_header_value_start;- else- {- state = s_header_field_start;- goto header_field_start;- }- break;- }-- case s_headers_almost_done:- headers_almost_done:- {- STRICT_CHECK(ch != LF);-- if (parser->flags & F_TRAILING) {- /* End of a chunked request */- CALLBACK2(message_complete);- state = NEW_MESSAGE();- break;- }-- nread = 0;-- if (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT) {- parser->upgrade = 1;- }-- /* Here we call the headers_complete callback. This is somewhat- * different than other callbacks because if the user returns 1, we- * will interpret that as saying that this message has no body. This- * is needed for the annoying case of recieving a response to a HEAD- * request.- */- if (settings->on_headers_complete) {- switch (settings->on_headers_complete(parser)) {- case 0:- break;-- case 1:- parser->flags |= F_SKIPBODY;- break;-- default:- parser->state = state;- SET_ERRNO(HPE_CB_headers_complete);- return p - data; /* Error */- }- }-- /* Exit, the rest of the connect is in a different protocol. */- if (parser->upgrade) {- CALLBACK2(message_complete);- return (p - data) + 1;- }-- if (parser->flags & F_SKIPBODY) {- CALLBACK2(message_complete);- state = NEW_MESSAGE();- } else if (parser->flags & F_CHUNKED) {- /* chunked encoding - ignore Content-Length header */- state = s_chunk_size_start;- } else {- if (parser->content_length == 0) {- /* Content-Length header given but zero: Content-Length: 0\r\n */- CALLBACK2(message_complete);- state = NEW_MESSAGE();- } else if (parser->content_length > 0) {- /* Content-Length header given and non-zero */- state = s_body_identity;- } else {- if (parser->type == HTTP_REQUEST || http_should_keep_alive(parser)) {- /* Assume content-length 0 - read the next */- CALLBACK2(message_complete);- state = NEW_MESSAGE();- } else {- /* Read body until EOF */- state = s_body_identity_eof;- }- }- }-- break;- }-- case s_body_identity:- to_read = (size_t)MIN(pe - p, parser->content_length);- if (to_read > 0) {- if (settings->on_body) settings->on_body(parser, p, to_read);- p += to_read - 1;- parser->content_length -= to_read;- if (parser->content_length == 0) {- CALLBACK2(message_complete);- state = NEW_MESSAGE();- }- }- break;-- /* read until EOF */- case s_body_identity_eof:- to_read = pe - p;- if (to_read > 0) {- if (settings->on_body) settings->on_body(parser, p, to_read);- p += to_read - 1;- }- break;-- case s_chunk_size_start:- {- assert(nread == 1);- assert(parser->flags & F_CHUNKED);-- unhex_val = unhex[(unsigned char)ch];- if (unhex_val == -1) {- SET_ERRNO(HPE_INVALID_CHUNK_SIZE);- goto error;- }-- parser->content_length = unhex_val;- state = s_chunk_size;- break;- }-- case s_chunk_size:- {- assert(parser->flags & F_CHUNKED);-- if (ch == CR) {- state = s_chunk_size_almost_done;- break;- }-- unhex_val = unhex[(unsigned char)ch];-- if (unhex_val == -1) {- if (ch == ';' || ch == ' ') {- state = s_chunk_parameters;- break;- }-- SET_ERRNO(HPE_INVALID_CHUNK_SIZE);- goto error;- }-- parser->content_length *= 16;- parser->content_length += unhex_val;- break;- }-- case s_chunk_parameters:- {- assert(parser->flags & F_CHUNKED);- /* just ignore this shit. TODO check for overflow */- if (ch == CR) {- state = s_chunk_size_almost_done;- break;- }- break;- }-- case s_chunk_size_almost_done:- {- assert(parser->flags & F_CHUNKED);- STRICT_CHECK(ch != LF);-- nread = 0;-- if (parser->content_length == 0) {- parser->flags |= F_TRAILING;- state = s_header_field_start;- } else {- state = s_chunk_data;- }- break;- }-- case s_chunk_data:- {- assert(parser->flags & F_CHUNKED);-- to_read = (size_t)MIN(pe - p, parser->content_length);-- if (to_read > 0) {- if (settings->on_body) settings->on_body(parser, p, to_read);- p += to_read - 1;- }-- if ((signed)to_read == parser->content_length) {- state = s_chunk_data_almost_done;- }-- parser->content_length -= to_read;- break;- }-- case s_chunk_data_almost_done:- assert(parser->flags & F_CHUNKED);- STRICT_CHECK(ch != CR);- state = s_chunk_data_done;- break;-- case s_chunk_data_done:- assert(parser->flags & F_CHUNKED);- STRICT_CHECK(ch != LF);- state = s_chunk_size_start;- break;-- default:- assert(0 && "unhandled state");- SET_ERRNO(HPE_INVALID_INTERNAL_STATE);- goto error;- }- }-- CALLBACK(header_field);- CALLBACK(header_value);- CALLBACK(url);-- parser->state = state;- parser->header_state = header_state;- parser->index = (unsigned char) index;- parser->nread = nread;-- return len;--error:- if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {- SET_ERRNO(HPE_UNKNOWN);- }-- return (p - data);-}---int-http_should_keep_alive (http_parser *parser)-{- if (parser->http_major > 0 && parser->http_minor > 0) {- /* HTTP/1.1 */- if (parser->flags & F_CONNECTION_CLOSE) {- return 0;- } else {- return 1;- }- } else {- /* HTTP/1.0 or earlier */- if (parser->flags & F_CONNECTION_KEEP_ALIVE) {- return 1;- } else {- return 0;- }- }-}---const char * http_method_str (enum http_method m)-{- return method_strings[m];-}---void-http_parser_init (http_parser *parser, enum http_parser_type t)-{- parser->type = t;- parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));- parser->nread = 0;- parser->upgrade = 0;- parser->flags = 0;- parser->method = 0;- parser->http_errno = 0;-}--const char *-http_errno_name(enum http_errno err) {- assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));- return http_strerror_tab[err].name;-}--const char *-http_errno_description(enum http_errno err) {- assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));- return http_strerror_tab[err].description;+#include "http_parser.h"+#include <assert.h>+#include <stddef.h>+#include <ctype.h>+#include <stdlib.h>+#include <string.h>+#include <limits.h>++#ifndef ULLONG_MAX+# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */+#endif++#ifndef MIN+# define MIN(a,b) ((a) < (b) ? (a) : (b))+#endif++#ifndef ARRAY_SIZE+# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))+#endif++#ifndef BIT_AT+# define BIT_AT(a, i) \+ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \+ (1 << ((unsigned int) (i) & 7))))+#endif++#ifndef ELEM_AT+# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))+#endif++#define SET_ERRNO(e) \+do { \+ parser->http_errno = (e); \+} while(0)+++/* Run the notify callback FOR, returning ER if it fails */+#define CALLBACK_NOTIFY_(FOR, ER) \+do { \+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \+ \+ if (settings->on_##FOR) { \+ if (0 != settings->on_##FOR(parser)) { \+ SET_ERRNO(HPE_CB_##FOR); \+ } \+ \+ /* We either errored above or got paused; get out */ \+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \+ return (ER); \+ } \+ } \+} while (0)++/* Run the notify callback FOR and consume the current byte */+#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1)++/* Run the notify callback FOR and don't consume the current byte */+#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data)++/* Run data callback FOR with LEN bytes, returning ER if it fails */+#define CALLBACK_DATA_(FOR, LEN, ER) \+do { \+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \+ \+ if (FOR##_mark) { \+ if (settings->on_##FOR) { \+ if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \+ SET_ERRNO(HPE_CB_##FOR); \+ } \+ \+ /* We either errored above or got paused; get out */ \+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \+ return (ER); \+ } \+ } \+ FOR##_mark = NULL; \+ } \+} while (0)+ +/* Run the data callback FOR and consume the current byte */+#define CALLBACK_DATA(FOR) \+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)++/* Run the data callback FOR and don't consume the current byte */+#define CALLBACK_DATA_NOADVANCE(FOR) \+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)++/* Set the mark FOR; non-destructive if mark is already set */+#define MARK(FOR) \+do { \+ if (!FOR##_mark) { \+ FOR##_mark = p; \+ } \+} while (0)+++#define PROXY_CONNECTION "proxy-connection"+#define CONNECTION "connection"+#define CONTENT_LENGTH "content-length"+#define TRANSFER_ENCODING "transfer-encoding"+#define UPGRADE "upgrade"+#define CHUNKED "chunked"+#define KEEP_ALIVE "keep-alive"+#define CLOSE "close"+++static const char *method_strings[] =+ {+#define XX(num, name, string) #string,+ HTTP_METHOD_MAP(XX)+#undef XX+ };+++/* Tokens as defined by rfc 2616. Also lowercases them.+ * token = 1*<any CHAR except CTLs or separators>+ * separators = "(" | ")" | "<" | ">" | "@"+ * | "," | ";" | ":" | "\" | <">+ * | "/" | "[" | "]" | "?" | "="+ * | "{" | "}" | SP | HT+ */+static const char tokens[256] = {+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */+ 0, 0, 0, 0, 0, 0, 0, 0,+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */+ 0, 0, 0, 0, 0, 0, 0, 0,+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */+ 0, 0, 0, 0, 0, 0, 0, 0,+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */+ 0, 0, 0, 0, 0, 0, 0, 0,+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */+ 0, '!', 0, '#', '$', '%', '&', '\'',+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */+ 0, 0, '*', '+', 0, '-', '.', 0,+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */+ '0', '1', '2', '3', '4', '5', '6', '7',+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */+ '8', '9', 0, 0, 0, 0, 0, 0,+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */+ 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g',+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */+ 'x', 'y', 'z', 0, 0, 0, '^', '_',+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */+ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */+ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */+ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */+ 'x', 'y', 'z', 0, '|', 0, '~', 0 };+++static const int8_t unhex[256] =+ {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1+ , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1+ ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1+ ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1+ };+++#if HTTP_PARSER_STRICT+# define T(v) 0+#else+# define T(v) v+#endif+++static const uint8_t normal_url_char[32] = {+/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,+/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */+ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,+/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,+/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,+/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */+ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,+/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,+/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,+/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };++#undef T++enum state+ { s_dead = 1 /* important that this is > 0 */++ , s_start_req_or_res+ , s_res_or_resp_H+ , s_start_res+ , s_res_H+ , s_res_HT+ , s_res_HTT+ , s_res_HTTP+ , s_res_first_http_major+ , s_res_http_major+ , s_res_first_http_minor+ , s_res_http_minor+ , s_res_first_status_code+ , s_res_status_code+ , s_res_status+ , s_res_line_almost_done++ , s_start_req++ , s_req_method+ , s_req_spaces_before_url+ , s_req_schema+ , s_req_schema_slash+ , s_req_schema_slash_slash+ , s_req_server_start+ , s_req_server+ , s_req_server_with_at+ , s_req_path+ , s_req_query_string_start+ , s_req_query_string+ , s_req_fragment_start+ , s_req_fragment+ , s_req_http_start+ , s_req_http_H+ , s_req_http_HT+ , s_req_http_HTT+ , s_req_http_HTTP+ , s_req_first_http_major+ , s_req_http_major+ , s_req_first_http_minor+ , s_req_http_minor+ , s_req_line_almost_done++ , s_header_field_start+ , s_header_field+ , s_header_value_start+ , s_header_value+ , s_header_value_lws++ , s_header_almost_done++ , s_chunk_size_start+ , s_chunk_size+ , s_chunk_parameters+ , s_chunk_size_almost_done++ , s_headers_almost_done+ , s_headers_done++ /* Important: 's_headers_done' must be the last 'header' state. All+ * states beyond this must be 'body' states. It is used for overflow+ * checking. See the PARSING_HEADER() macro.+ */++ , s_chunk_data+ , s_chunk_data_almost_done+ , s_chunk_data_done++ , s_body_identity+ , s_body_identity_eof++ , s_message_done+ };+++#define PARSING_HEADER(state) (state <= s_headers_done)+++enum header_states+ { h_general = 0+ , h_C+ , h_CO+ , h_CON++ , h_matching_connection+ , h_matching_proxy_connection+ , h_matching_content_length+ , h_matching_transfer_encoding+ , h_matching_upgrade++ , h_connection+ , h_content_length+ , h_transfer_encoding+ , h_upgrade++ , h_matching_transfer_encoding_chunked+ , h_matching_connection_keep_alive+ , h_matching_connection_close++ , h_transfer_encoding_chunked+ , h_connection_keep_alive+ , h_connection_close+ };++enum http_host_state+ {+ s_http_host_dead = 1+ , s_http_userinfo_start+ , s_http_userinfo+ , s_http_host_start+ , s_http_host_v6_start+ , s_http_host+ , s_http_host_v6+ , s_http_host_v6_end+ , s_http_host_port_start+ , s_http_host_port+};++/* Macros for character classes; depends on strict-mode */+#define CR '\r'+#define LF '\n'+#define LOWER(c) (unsigned char)(c | 0x20)+#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z')+#define IS_NUM(c) ((c) >= '0' && (c) <= '9')+#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))+#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))+#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \+ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \+ (c) == ')')+#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \+ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \+ (c) == '$' || (c) == ',')++#if HTTP_PARSER_STRICT+#define TOKEN(c) (tokens[(unsigned char)c])+#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))+#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')+#else+#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])+#define IS_URL_CHAR(c) \+ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))+#define IS_HOST_CHAR(c) \+ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')+#endif+++#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)+++#if HTTP_PARSER_STRICT+# define STRICT_CHECK(cond) \+do { \+ if (cond) { \+ SET_ERRNO(HPE_STRICT); \+ goto error; \+ } \+} while (0)+# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)+#else+# define STRICT_CHECK(cond)+# define NEW_MESSAGE() start_state+#endif+++/* Map errno values to strings for human-readable output */+#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s },+static struct {+ const char *name;+ const char *description;+} http_strerror_tab[] = {+ HTTP_ERRNO_MAP(HTTP_STRERROR_GEN)+};+#undef HTTP_STRERROR_GEN++int http_message_needs_eof(const http_parser *parser);++/* Our URL parser.+ *+ * This is designed to be shared by http_parser_execute() for URL validation,+ * hence it has a state transition + byte-for-byte interface. In addition, it+ * is meant to be embedded in http_parser_parse_url(), which does the dirty+ * work of turning state transitions URL components for its API.+ *+ * This function should only be invoked with non-space characters. It is+ * assumed that the caller cares about (and can detect) the transition between+ * URL and non-URL states by looking for these.+ */+static enum state+parse_url_char(enum state s, const char ch)+{+ if (ch == ' ' || ch == '\r' || ch == '\n') {+ return s_dead;+ }++#if HTTP_PARSER_STRICT+ if (ch == '\t' || ch == '\f') {+ return s_dead;+ }+#endif++ switch (s) {+ case s_req_spaces_before_url:+ /* Proxied requests are followed by scheme of an absolute URI (alpha).+ * All methods except CONNECT are followed by '/' or '*'.+ */++ if (ch == '/' || ch == '*') {+ return s_req_path;+ }++ if (IS_ALPHA(ch)) {+ return s_req_schema;+ }++ break;++ case s_req_schema:+ if (IS_ALPHA(ch)) {+ return s;+ }++ if (ch == ':') {+ return s_req_schema_slash;+ }++ break;++ case s_req_schema_slash:+ if (ch == '/') {+ return s_req_schema_slash_slash;+ }++ break;++ case s_req_schema_slash_slash:+ if (ch == '/') {+ return s_req_server_start;+ }++ break;++ case s_req_server_with_at:+ if (ch == '@') {+ return s_dead;+ }++ /* FALLTHROUGH */+ case s_req_server_start:+ case s_req_server:+ if (ch == '/') {+ return s_req_path;+ }++ if (ch == '?') {+ return s_req_query_string_start;+ }++ if (ch == '@') {+ return s_req_server_with_at;+ }++ if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {+ return s_req_server;+ }++ break;++ case s_req_path:+ if (IS_URL_CHAR(ch)) {+ return s;+ }++ switch (ch) {+ case '?':+ return s_req_query_string_start;++ case '#':+ return s_req_fragment_start;+ }++ break;++ case s_req_query_string_start:+ case s_req_query_string:+ if (IS_URL_CHAR(ch)) {+ return s_req_query_string;+ }++ switch (ch) {+ case '?':+ /* allow extra '?' in query string */+ return s_req_query_string;++ case '#':+ return s_req_fragment_start;+ }++ break;++ case s_req_fragment_start:+ if (IS_URL_CHAR(ch)) {+ return s_req_fragment;+ }++ switch (ch) {+ case '?':+ return s_req_fragment;++ case '#':+ return s;+ }++ break;++ case s_req_fragment:+ if (IS_URL_CHAR(ch)) {+ return s;+ }++ switch (ch) {+ case '?':+ case '#':+ return s;+ }++ break;++ default:+ break;+ }++ /* We should never fall out of the switch above unless there's an error */+ return s_dead;+}++size_t http_parser_execute (http_parser *parser,+ const http_parser_settings *settings,+ const char *data,+ size_t len)+{+ char c, ch;+ int8_t unhex_val;+ const char *p = data;+ const char *header_field_mark = 0;+ const char *header_value_mark = 0;+ const char *url_mark = 0;+ const char *body_mark = 0;++ /* We're in an error state. Don't bother doing anything. */+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {+ return 0;+ }++ if (len == 0) {+ switch (parser->state) {+ case s_body_identity_eof:+ /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if+ * we got paused.+ */+ CALLBACK_NOTIFY_NOADVANCE(message_complete);+ return 0;++ case s_dead:+ case s_start_req_or_res:+ case s_start_res:+ case s_start_req:+ return 0;++ default:+ SET_ERRNO(HPE_INVALID_EOF_STATE);+ return 1;+ }+ }+++ if (parser->state == s_header_field)+ header_field_mark = data;+ if (parser->state == s_header_value)+ header_value_mark = data;+ switch (parser->state) {+ case s_req_path:+ case s_req_schema:+ case s_req_schema_slash:+ case s_req_schema_slash_slash:+ case s_req_server_start:+ case s_req_server:+ case s_req_server_with_at:+ case s_req_query_string_start:+ case s_req_query_string:+ case s_req_fragment_start:+ case s_req_fragment:+ url_mark = data;+ break;+ }++ for (p=data; p != data + len; p++) {+ ch = *p;++ if (PARSING_HEADER(parser->state)) {+ ++parser->nread;+ /* Buffer overflow attack */+ if (parser->nread > HTTP_MAX_HEADER_SIZE) {+ SET_ERRNO(HPE_HEADER_OVERFLOW);+ goto error;+ }+ }++ reexecute_byte:+ switch (parser->state) {++ case s_dead:+ /* this state is used after a 'Connection: close' message+ * the parser will error out if it reads another message+ */+ if (ch == CR || ch == LF)+ break;++ SET_ERRNO(HPE_CLOSED_CONNECTION);+ goto error;++ case s_start_req_or_res:+ {+ if (ch == CR || ch == LF)+ break;+ parser->flags = 0;+ parser->content_length = ULLONG_MAX;++ if (ch == 'H') {+ parser->state = s_res_or_resp_H;++ CALLBACK_NOTIFY(message_begin);+ } else {+ parser->type = HTTP_REQUEST;+ parser->state = s_start_req;+ goto reexecute_byte;+ }++ break;+ }++ case s_res_or_resp_H:+ if (ch == 'T') {+ parser->type = HTTP_RESPONSE;+ parser->state = s_res_HT;+ } else {+ if (ch != 'E') {+ SET_ERRNO(HPE_INVALID_CONSTANT);+ goto error;+ }++ parser->type = HTTP_REQUEST;+ parser->method = HTTP_HEAD;+ parser->index = 2;+ parser->state = s_req_method;+ }+ break;++ case s_start_res:+ {+ parser->flags = 0;+ parser->content_length = ULLONG_MAX;++ switch (ch) {+ case 'H':+ parser->state = s_res_H;+ break;++ case CR:+ case LF:+ break;++ default:+ SET_ERRNO(HPE_INVALID_CONSTANT);+ goto error;+ }++ CALLBACK_NOTIFY(message_begin);+ break;+ }++ case s_res_H:+ STRICT_CHECK(ch != 'T');+ parser->state = s_res_HT;+ break;++ case s_res_HT:+ STRICT_CHECK(ch != 'T');+ parser->state = s_res_HTT;+ break;++ case s_res_HTT:+ STRICT_CHECK(ch != 'P');+ parser->state = s_res_HTTP;+ break;++ case s_res_HTTP:+ STRICT_CHECK(ch != '/');+ parser->state = s_res_first_http_major;+ break;++ case s_res_first_http_major:+ if (ch < '0' || ch > '9') {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ parser->http_major = ch - '0';+ parser->state = s_res_http_major;+ break;++ /* major HTTP version or dot */+ case s_res_http_major:+ {+ if (ch == '.') {+ parser->state = s_res_first_http_minor;+ break;+ }++ if (!IS_NUM(ch)) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ parser->http_major *= 10;+ parser->http_major += ch - '0';++ if (parser->http_major > 999) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ break;+ }++ /* first digit of minor HTTP version */+ case s_res_first_http_minor:+ if (!IS_NUM(ch)) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ parser->http_minor = ch - '0';+ parser->state = s_res_http_minor;+ break;++ /* minor HTTP version or end of request line */+ case s_res_http_minor:+ {+ if (ch == ' ') {+ parser->state = s_res_first_status_code;+ break;+ }++ if (!IS_NUM(ch)) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ parser->http_minor *= 10;+ parser->http_minor += ch - '0';++ if (parser->http_minor > 999) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ break;+ }++ case s_res_first_status_code:+ {+ if (!IS_NUM(ch)) {+ if (ch == ' ') {+ break;+ }++ SET_ERRNO(HPE_INVALID_STATUS);+ goto error;+ }+ parser->status_code = ch - '0';+ parser->state = s_res_status_code;+ break;+ }++ case s_res_status_code:+ {+ if (!IS_NUM(ch)) {+ switch (ch) {+ case ' ':+ parser->state = s_res_status;+ break;+ case CR:+ parser->state = s_res_line_almost_done;+ break;+ case LF:+ parser->state = s_header_field_start;+ break;+ default:+ SET_ERRNO(HPE_INVALID_STATUS);+ goto error;+ }+ break;+ }++ parser->status_code *= 10;+ parser->status_code += ch - '0';++ if (parser->status_code > 999) {+ SET_ERRNO(HPE_INVALID_STATUS);+ goto error;+ }++ break;+ }++ case s_res_status:+ /* the human readable status. e.g. "NOT FOUND"+ * we are not humans so just ignore this */+ if (ch == CR) {+ parser->state = s_res_line_almost_done;+ break;+ }++ if (ch == LF) {+ parser->state = s_header_field_start;+ break;+ }+ break;++ case s_res_line_almost_done:+ STRICT_CHECK(ch != LF);+ parser->state = s_header_field_start;+ break;++ case s_start_req:+ {+ if (ch == CR || ch == LF)+ break;+ parser->flags = 0;+ parser->content_length = ULLONG_MAX;++ if (!IS_ALPHA(ch)) {+ SET_ERRNO(HPE_INVALID_METHOD);+ goto error;+ }++ parser->method = (enum http_method) 0;+ parser->index = 1;+ switch (ch) {+ case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;+ case 'D': parser->method = HTTP_DELETE; break;+ case 'G': parser->method = HTTP_GET; break;+ case 'H': parser->method = HTTP_HEAD; break;+ case 'L': parser->method = HTTP_LOCK; break;+ case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;+ case 'N': parser->method = HTTP_NOTIFY; break;+ case 'O': parser->method = HTTP_OPTIONS; break;+ case 'P': parser->method = HTTP_POST;+ /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */+ break;+ case 'R': parser->method = HTTP_REPORT; break;+ case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;+ case 'T': parser->method = HTTP_TRACE; break;+ case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;+ default:+ SET_ERRNO(HPE_INVALID_METHOD);+ goto error;+ }+ parser->state = s_req_method;++ CALLBACK_NOTIFY(message_begin);++ break;+ }++ case s_req_method:+ {+ const char *matcher;+ if (ch == '\0') {+ SET_ERRNO(HPE_INVALID_METHOD);+ goto error;+ }++ matcher = method_strings[parser->method];+ if (ch == ' ' && matcher[parser->index] == '\0') {+ parser->state = s_req_spaces_before_url;+ } else if (ch == matcher[parser->index]) {+ ; /* nada */+ } else if (parser->method == HTTP_CONNECT) {+ if (parser->index == 1 && ch == 'H') {+ parser->method = HTTP_CHECKOUT;+ } else if (parser->index == 2 && ch == 'P') {+ parser->method = HTTP_COPY;+ } else {+ goto error;+ }+ } else if (parser->method == HTTP_MKCOL) {+ if (parser->index == 1 && ch == 'O') {+ parser->method = HTTP_MOVE;+ } else if (parser->index == 1 && ch == 'E') {+ parser->method = HTTP_MERGE;+ } else if (parser->index == 1 && ch == '-') {+ parser->method = HTTP_MSEARCH;+ } else if (parser->index == 2 && ch == 'A') {+ parser->method = HTTP_MKACTIVITY;+ } else {+ goto error;+ }+ } else if (parser->method == HTTP_SUBSCRIBE) {+ if (parser->index == 1 && ch == 'E') {+ parser->method = HTTP_SEARCH;+ } else {+ goto error;+ }+ } else if (parser->index == 1 && parser->method == HTTP_POST) {+ if (ch == 'R') {+ parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */+ } else if (ch == 'U') {+ parser->method = HTTP_PUT; /* or HTTP_PURGE */+ } else if (ch == 'A') {+ parser->method = HTTP_PATCH;+ } else {+ goto error;+ }+ } else if (parser->index == 2) {+ if (parser->method == HTTP_PUT) {+ if (ch == 'R') parser->method = HTTP_PURGE;+ } else if (parser->method == HTTP_UNLOCK) {+ if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE;+ }+ } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {+ parser->method = HTTP_PROPPATCH;+ } else {+ SET_ERRNO(HPE_INVALID_METHOD);+ goto error;+ }++ ++parser->index;+ break;+ }++ case s_req_spaces_before_url:+ {+ if (ch == ' ') break;++ MARK(url);+ if (parser->method == HTTP_CONNECT) {+ parser->state = s_req_server_start;+ }++ parser->state = parse_url_char((enum state)parser->state, ch);+ if (parser->state == s_dead) {+ SET_ERRNO(HPE_INVALID_URL);+ goto error;+ }++ break;+ }++ case s_req_schema:+ case s_req_schema_slash:+ case s_req_schema_slash_slash:+ case s_req_server_start:+ {+ switch (ch) {+ /* No whitespace allowed here */+ case ' ':+ case CR:+ case LF:+ SET_ERRNO(HPE_INVALID_URL);+ goto error;+ default:+ parser->state = parse_url_char((enum state)parser->state, ch);+ if (parser->state == s_dead) {+ SET_ERRNO(HPE_INVALID_URL);+ goto error;+ }+ }++ break;+ }++ case s_req_server:+ case s_req_server_with_at:+ case s_req_path:+ case s_req_query_string_start:+ case s_req_query_string:+ case s_req_fragment_start:+ case s_req_fragment:+ {+ switch (ch) {+ case ' ':+ parser->state = s_req_http_start;+ CALLBACK_DATA(url);+ break;+ case CR:+ case LF:+ parser->http_major = 0;+ parser->http_minor = 9;+ parser->state = (ch == CR) ?+ s_req_line_almost_done :+ s_header_field_start;+ CALLBACK_DATA(url);+ break;+ default:+ parser->state = parse_url_char((enum state)parser->state, ch);+ if (parser->state == s_dead) {+ SET_ERRNO(HPE_INVALID_URL);+ goto error;+ }+ }+ break;+ }++ case s_req_http_start:+ switch (ch) {+ case 'H':+ parser->state = s_req_http_H;+ break;+ case ' ':+ break;+ default:+ SET_ERRNO(HPE_INVALID_CONSTANT);+ goto error;+ }+ break;++ case s_req_http_H:+ STRICT_CHECK(ch != 'T');+ parser->state = s_req_http_HT;+ break;++ case s_req_http_HT:+ STRICT_CHECK(ch != 'T');+ parser->state = s_req_http_HTT;+ break;++ case s_req_http_HTT:+ STRICT_CHECK(ch != 'P');+ parser->state = s_req_http_HTTP;+ break;++ case s_req_http_HTTP:+ STRICT_CHECK(ch != '/');+ parser->state = s_req_first_http_major;+ break;++ /* first digit of major HTTP version */+ case s_req_first_http_major:+ if (ch < '1' || ch > '9') {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ parser->http_major = ch - '0';+ parser->state = s_req_http_major;+ break;++ /* major HTTP version or dot */+ case s_req_http_major:+ {+ if (ch == '.') {+ parser->state = s_req_first_http_minor;+ break;+ }++ if (!IS_NUM(ch)) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ parser->http_major *= 10;+ parser->http_major += ch - '0';++ if (parser->http_major > 999) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ break;+ }++ /* first digit of minor HTTP version */+ case s_req_first_http_minor:+ if (!IS_NUM(ch)) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ parser->http_minor = ch - '0';+ parser->state = s_req_http_minor;+ break;++ /* minor HTTP version or end of request line */+ case s_req_http_minor:+ {+ if (ch == CR) {+ parser->state = s_req_line_almost_done;+ break;+ }++ if (ch == LF) {+ parser->state = s_header_field_start;+ break;+ }++ /* XXX allow spaces after digit? */++ if (!IS_NUM(ch)) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ parser->http_minor *= 10;+ parser->http_minor += ch - '0';++ if (parser->http_minor > 999) {+ SET_ERRNO(HPE_INVALID_VERSION);+ goto error;+ }++ break;+ }++ /* end of request line */+ case s_req_line_almost_done:+ {+ if (ch != LF) {+ SET_ERRNO(HPE_LF_EXPECTED);+ goto error;+ }++ parser->state = s_header_field_start;+ break;+ }++ case s_header_field_start:+ {+ if (ch == CR) {+ parser->state = s_headers_almost_done;+ break;+ }++ if (ch == LF) {+ /* they might be just sending \n instead of \r\n so this would be+ * the second \n to denote the end of headers*/+ parser->state = s_headers_almost_done;+ goto reexecute_byte;+ }++ c = TOKEN(ch);++ if (!c) {+ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);+ goto error;+ }++ MARK(header_field);++ parser->index = 0;+ parser->state = s_header_field;++ switch (c) {+ case 'c':+ parser->header_state = h_C;+ break;++ case 'p':+ parser->header_state = h_matching_proxy_connection;+ break;++ case 't':+ parser->header_state = h_matching_transfer_encoding;+ break;++ case 'u':+ parser->header_state = h_matching_upgrade;+ break;++ default:+ parser->header_state = h_general;+ break;+ }+ break;+ }++ case s_header_field:+ {+ c = TOKEN(ch);++ if (c) {+ switch (parser->header_state) {+ case h_general:+ break;++ case h_C:+ parser->index++;+ parser->header_state = (c == 'o' ? h_CO : h_general);+ break;++ case h_CO:+ parser->index++;+ parser->header_state = (c == 'n' ? h_CON : h_general);+ break;++ case h_CON:+ parser->index++;+ switch (c) {+ case 'n':+ parser->header_state = h_matching_connection;+ break;+ case 't':+ parser->header_state = h_matching_content_length;+ break;+ default:+ parser->header_state = h_general;+ break;+ }+ break;++ /* connection */++ case h_matching_connection:+ parser->index++;+ if (parser->index > sizeof(CONNECTION)-1+ || c != CONNECTION[parser->index]) {+ parser->header_state = h_general;+ } else if (parser->index == sizeof(CONNECTION)-2) {+ parser->header_state = h_connection;+ }+ break;++ /* proxy-connection */++ case h_matching_proxy_connection:+ parser->index++;+ if (parser->index > sizeof(PROXY_CONNECTION)-1+ || c != PROXY_CONNECTION[parser->index]) {+ parser->header_state = h_general;+ } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {+ parser->header_state = h_connection;+ }+ break;++ /* content-length */++ case h_matching_content_length:+ parser->index++;+ if (parser->index > sizeof(CONTENT_LENGTH)-1+ || c != CONTENT_LENGTH[parser->index]) {+ parser->header_state = h_general;+ } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {+ parser->header_state = h_content_length;+ }+ break;++ /* transfer-encoding */++ case h_matching_transfer_encoding:+ parser->index++;+ if (parser->index > sizeof(TRANSFER_ENCODING)-1+ || c != TRANSFER_ENCODING[parser->index]) {+ parser->header_state = h_general;+ } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {+ parser->header_state = h_transfer_encoding;+ }+ break;++ /* upgrade */++ case h_matching_upgrade:+ parser->index++;+ if (parser->index > sizeof(UPGRADE)-1+ || c != UPGRADE[parser->index]) {+ parser->header_state = h_general;+ } else if (parser->index == sizeof(UPGRADE)-2) {+ parser->header_state = h_upgrade;+ }+ break;++ case h_connection:+ case h_content_length:+ case h_transfer_encoding:+ case h_upgrade:+ if (ch != ' ') parser->header_state = h_general;+ break;++ default:+ assert(0 && "Unknown header_state");+ break;+ }+ break;+ }++ if (ch == ':') {+ parser->state = s_header_value_start;+ CALLBACK_DATA(header_field);+ break;+ }++ if (ch == CR) {+ parser->state = s_header_almost_done;+ CALLBACK_DATA(header_field);+ break;+ }++ if (ch == LF) {+ parser->state = s_header_field_start;+ CALLBACK_DATA(header_field);+ break;+ }++ SET_ERRNO(HPE_INVALID_HEADER_TOKEN);+ goto error;+ }++ case s_header_value_start:+ {+ if (ch == ' ' || ch == '\t') break;++ MARK(header_value);++ parser->state = s_header_value;+ parser->index = 0;++ if (ch == CR) {+ parser->header_state = h_general;+ parser->state = s_header_almost_done;+ CALLBACK_DATA(header_value);+ break;+ }++ if (ch == LF) {+ parser->state = s_header_field_start;+ CALLBACK_DATA(header_value);+ break;+ }++ c = LOWER(ch);++ switch (parser->header_state) {+ case h_upgrade:+ parser->flags |= F_UPGRADE;+ parser->header_state = h_general;+ break;++ case h_transfer_encoding:+ /* looking for 'Transfer-Encoding: chunked' */+ if ('c' == c) {+ parser->header_state = h_matching_transfer_encoding_chunked;+ } else {+ parser->header_state = h_general;+ }+ break;++ case h_content_length:+ if (!IS_NUM(ch)) {+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);+ goto error;+ }++ parser->content_length = ch - '0';+ break;++ case h_connection:+ /* looking for 'Connection: keep-alive' */+ if (c == 'k') {+ parser->header_state = h_matching_connection_keep_alive;+ /* looking for 'Connection: close' */+ } else if (c == 'c') {+ parser->header_state = h_matching_connection_close;+ } else {+ parser->header_state = h_general;+ }+ break;++ default:+ parser->header_state = h_general;+ break;+ }+ break;+ }++ case s_header_value:+ {++ if (ch == CR) {+ parser->state = s_header_almost_done;+ CALLBACK_DATA(header_value);+ break;+ }++ if (ch == LF) {+ parser->state = s_header_almost_done;+ CALLBACK_DATA_NOADVANCE(header_value);+ goto reexecute_byte;+ }++ c = LOWER(ch);++ switch (parser->header_state) {+ case h_general:+ break;++ case h_connection:+ case h_transfer_encoding:+ assert(0 && "Shouldn't get here.");+ break;++ case h_content_length:+ {+ uint64_t t;++ if (ch == ' ') break;++ if (!IS_NUM(ch)) {+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);+ goto error;+ }++ t = parser->content_length;+ t *= 10;+ t += ch - '0';++ /* Overflow? */+ if (t < parser->content_length || t == ULLONG_MAX) {+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);+ goto error;+ }++ parser->content_length = t;+ break;+ }++ /* Transfer-Encoding: chunked */+ case h_matching_transfer_encoding_chunked:+ parser->index++;+ if (parser->index > sizeof(CHUNKED)-1+ || c != CHUNKED[parser->index]) {+ parser->header_state = h_general;+ } else if (parser->index == sizeof(CHUNKED)-2) {+ parser->header_state = h_transfer_encoding_chunked;+ }+ break;++ /* looking for 'Connection: keep-alive' */+ case h_matching_connection_keep_alive:+ parser->index++;+ if (parser->index > sizeof(KEEP_ALIVE)-1+ || c != KEEP_ALIVE[parser->index]) {+ parser->header_state = h_general;+ } else if (parser->index == sizeof(KEEP_ALIVE)-2) {+ parser->header_state = h_connection_keep_alive;+ }+ break;++ /* looking for 'Connection: close' */+ case h_matching_connection_close:+ parser->index++;+ if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {+ parser->header_state = h_general;+ } else if (parser->index == sizeof(CLOSE)-2) {+ parser->header_state = h_connection_close;+ }+ break;++ case h_transfer_encoding_chunked:+ case h_connection_keep_alive:+ case h_connection_close:+ if (ch != ' ') parser->header_state = h_general;+ break;++ default:+ parser->state = s_header_value;+ parser->header_state = h_general;+ break;+ }+ break;+ }++ case s_header_almost_done:+ {+ STRICT_CHECK(ch != LF);++ parser->state = s_header_value_lws;++ switch (parser->header_state) {+ case h_connection_keep_alive:+ parser->flags |= F_CONNECTION_KEEP_ALIVE;+ break;+ case h_connection_close:+ parser->flags |= F_CONNECTION_CLOSE;+ break;+ case h_transfer_encoding_chunked:+ parser->flags |= F_CHUNKED;+ break;+ default:+ break;+ }++ break;+ }++ case s_header_value_lws:+ {+ if (ch == ' ' || ch == '\t')+ parser->state = s_header_value_start;+ else+ {+ parser->state = s_header_field_start;+ goto reexecute_byte;+ }+ break;+ }++ case s_headers_almost_done:+ {+ STRICT_CHECK(ch != LF);++ if (parser->flags & F_TRAILING) {+ /* End of a chunked request */+ parser->state = NEW_MESSAGE();+ CALLBACK_NOTIFY(message_complete);+ break;+ }++ parser->state = s_headers_done;++ /* Set this here so that on_headers_complete() callbacks can see it */+ parser->upgrade =+ (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT);++ /* Here we call the headers_complete callback. This is somewhat+ * different than other callbacks because if the user returns 1, we+ * will interpret that as saying that this message has no body. This+ * is needed for the annoying case of recieving a response to a HEAD+ * request.+ *+ * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so+ * we have to simulate it by handling a change in errno below.+ */+ if (settings->on_headers_complete) {+ switch (settings->on_headers_complete(parser)) {+ case 0:+ break;++ case 1:+ parser->flags |= F_SKIPBODY;+ break;++ default:+ SET_ERRNO(HPE_CB_headers_complete);+ return p - data; /* Error */+ }+ }++ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {+ return p - data;+ }++ goto reexecute_byte;+ }++ case s_headers_done:+ {+ STRICT_CHECK(ch != LF);++ parser->nread = 0;++ /* Exit, the rest of the connect is in a different protocol. */+ if (parser->upgrade) {+ parser->state = NEW_MESSAGE();+ CALLBACK_NOTIFY(message_complete);+ return (p - data) + 1;+ }++ if (parser->flags & F_SKIPBODY) {+ parser->state = NEW_MESSAGE();+ CALLBACK_NOTIFY(message_complete);+ } else if (parser->flags & F_CHUNKED) {+ /* chunked encoding - ignore Content-Length header */+ parser->state = s_chunk_size_start;+ } else {+ if (parser->content_length == 0) {+ /* Content-Length header given but zero: Content-Length: 0\r\n */+ parser->state = NEW_MESSAGE();+ CALLBACK_NOTIFY(message_complete);+ } else if (parser->content_length != ULLONG_MAX) {+ /* Content-Length header given and non-zero */+ parser->state = s_body_identity;+ } else {+ if (parser->type == HTTP_REQUEST ||+ !http_message_needs_eof(parser)) {+ /* Assume content-length 0 - read the next */+ parser->state = NEW_MESSAGE();+ CALLBACK_NOTIFY(message_complete);+ } else {+ /* Read body until EOF */+ parser->state = s_body_identity_eof;+ }+ }+ }++ break;+ }++ case s_body_identity:+ {+ uint64_t to_read = MIN(parser->content_length,+ (uint64_t) ((data + len) - p));++ assert(parser->content_length != 0+ && parser->content_length != ULLONG_MAX);++ /* The difference between advancing content_length and p is because+ * the latter will automaticaly advance on the next loop iteration.+ * Further, if content_length ends up at 0, we want to see the last+ * byte again for our message complete callback.+ */+ MARK(body);+ parser->content_length -= to_read;+ p += to_read - 1;++ if (parser->content_length == 0) {+ parser->state = s_message_done;++ /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.+ *+ * The alternative to doing this is to wait for the next byte to+ * trigger the data callback, just as in every other case. The+ * problem with this is that this makes it difficult for the test+ * harness to distinguish between complete-on-EOF and+ * complete-on-length. It's not clear that this distinction is+ * important for applications, but let's keep it for now.+ */+ CALLBACK_DATA_(body, p - body_mark + 1, p - data);+ goto reexecute_byte;+ }++ break;+ }++ /* read until EOF */+ case s_body_identity_eof:+ MARK(body);+ p = data + len - 1;++ break;++ case s_message_done:+ parser->state = NEW_MESSAGE();+ CALLBACK_NOTIFY(message_complete);+ break;++ case s_chunk_size_start:+ {+ assert(parser->nread == 1);+ assert(parser->flags & F_CHUNKED);++ unhex_val = unhex[(unsigned char)ch];+ if (unhex_val == -1) {+ SET_ERRNO(HPE_INVALID_CHUNK_SIZE);+ goto error;+ }++ parser->content_length = unhex_val;+ parser->state = s_chunk_size;+ break;+ }++ case s_chunk_size:+ {+ uint64_t t;++ assert(parser->flags & F_CHUNKED);++ if (ch == CR) {+ parser->state = s_chunk_size_almost_done;+ break;+ }++ unhex_val = unhex[(unsigned char)ch];++ if (unhex_val == -1) {+ if (ch == ';' || ch == ' ') {+ parser->state = s_chunk_parameters;+ break;+ }++ SET_ERRNO(HPE_INVALID_CHUNK_SIZE);+ goto error;+ }++ t = parser->content_length;+ t *= 16;+ t += unhex_val;++ /* Overflow? */+ if (t < parser->content_length || t == ULLONG_MAX) {+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);+ goto error;+ }++ parser->content_length = t;+ break;+ }++ case s_chunk_parameters:+ {+ assert(parser->flags & F_CHUNKED);+ /* just ignore this. TODO check for overflow */+ if (ch == CR) {+ parser->state = s_chunk_size_almost_done;+ break;+ }+ break;+ }++ case s_chunk_size_almost_done:+ {+ assert(parser->flags & F_CHUNKED);+ STRICT_CHECK(ch != LF);++ parser->nread = 0;++ if (parser->content_length == 0) {+ parser->flags |= F_TRAILING;+ parser->state = s_header_field_start;+ } else {+ parser->state = s_chunk_data;+ }+ break;+ }++ case s_chunk_data:+ {+ uint64_t to_read = MIN(parser->content_length,+ (uint64_t) ((data + len) - p));++ assert(parser->flags & F_CHUNKED);+ assert(parser->content_length != 0+ && parser->content_length != ULLONG_MAX);++ /* See the explanation in s_body_identity for why the content+ * length and data pointers are managed this way.+ */+ MARK(body);+ parser->content_length -= to_read;+ p += to_read - 1;++ if (parser->content_length == 0) {+ parser->state = s_chunk_data_almost_done;+ }++ break;+ }++ case s_chunk_data_almost_done:+ assert(parser->flags & F_CHUNKED);+ assert(parser->content_length == 0);+ STRICT_CHECK(ch != CR);+ parser->state = s_chunk_data_done;+ CALLBACK_DATA(body);+ break;++ case s_chunk_data_done:+ assert(parser->flags & F_CHUNKED);+ STRICT_CHECK(ch != LF);+ parser->nread = 0;+ parser->state = s_chunk_size_start;+ break;++ default:+ assert(0 && "unhandled state");+ SET_ERRNO(HPE_INVALID_INTERNAL_STATE);+ goto error;+ }+ }++ /* Run callbacks for any marks that we have leftover after we ran our of+ * bytes. There should be at most one of these set, so it's OK to invoke+ * them in series (unset marks will not result in callbacks).+ *+ * We use the NOADVANCE() variety of callbacks here because 'p' has already+ * overflowed 'data' and this allows us to correct for the off-by-one that+ * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'+ * value that's in-bounds).+ */++ assert(((header_field_mark ? 1 : 0) ++ (header_value_mark ? 1 : 0) ++ (url_mark ? 1 : 0) ++ (body_mark ? 1 : 0)) <= 1);++ CALLBACK_DATA_NOADVANCE(header_field);+ CALLBACK_DATA_NOADVANCE(header_value);+ CALLBACK_DATA_NOADVANCE(url);+ CALLBACK_DATA_NOADVANCE(body);++ return len;++error:+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {+ SET_ERRNO(HPE_UNKNOWN);+ }++ return (p - data);+}+++/* Does the parser need to see an EOF to find the end of the message? */+int+http_message_needs_eof (const http_parser *parser)+{+ if (parser->type == HTTP_REQUEST) {+ return 0;+ }++ /* See RFC 2616 section 4.4 */+ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */+ parser->status_code == 204 || /* No Content */+ parser->status_code == 304 || /* Not Modified */+ parser->flags & F_SKIPBODY) { /* response to a HEAD request */+ return 0;+ }++ if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {+ return 0;+ }++ return 1;+}+++int+http_should_keep_alive (const http_parser *parser)+{+ if (parser->http_major > 0 && parser->http_minor > 0) {+ /* HTTP/1.1 */+ if (parser->flags & F_CONNECTION_CLOSE) {+ return 0;+ }+ } else {+ /* HTTP/1.0 or earlier */+ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {+ return 0;+ }+ }++ return !http_message_needs_eof(parser);+}+++const char *+http_method_str (enum http_method m)+{+ return ELEM_AT(method_strings, m, "<unknown>");+}+++void+http_parser_init (http_parser *parser, enum http_parser_type t)+{+ void *data = parser->data; /* preserve application data */+ memset(parser, 0, sizeof(*parser));+ parser->data = data;+ parser->type = t;+ parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));+ parser->http_errno = HPE_OK;+}++const char *+http_errno_name(enum http_errno err) {+ assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));+ return http_strerror_tab[err].name;+}++const char *+http_errno_description(enum http_errno err) {+ assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));+ return http_strerror_tab[err].description;+}++static enum http_host_state+http_parse_host_char(enum http_host_state s, const char ch) {+ switch(s) {+ case s_http_userinfo:+ case s_http_userinfo_start:+ if (ch == '@') {+ return s_http_host_start;+ }++ if (IS_USERINFO_CHAR(ch)) {+ return s_http_userinfo;+ }+ break;++ case s_http_host_start:+ if (ch == '[') {+ return s_http_host_v6_start;+ }++ if (IS_HOST_CHAR(ch)) {+ return s_http_host;+ }++ break;++ case s_http_host:+ if (IS_HOST_CHAR(ch)) {+ return s_http_host;+ }++ /* FALLTHROUGH */+ case s_http_host_v6_end:+ if (ch == ':') {+ return s_http_host_port_start;+ }++ break;++ case s_http_host_v6:+ if (ch == ']') {+ return s_http_host_v6_end;+ }++ /* FALLTHROUGH */+ case s_http_host_v6_start:+ if (IS_HEX(ch) || ch == ':') {+ return s_http_host_v6;+ }++ break;++ case s_http_host_port:+ case s_http_host_port_start:+ if (IS_NUM(ch)) {+ return s_http_host_port;+ }++ break;++ default:+ break;+ }+ return s_http_host_dead;+}++static int+http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {+ enum http_host_state s;++ const char *p;+ size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;++ u->field_data[UF_HOST].len = 0;++ s = found_at ? s_http_userinfo_start : s_http_host_start;++ for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {+ enum http_host_state new_s = http_parse_host_char(s, *p);++ if (new_s == s_http_host_dead) {+ return 1;+ }++ switch(new_s) {+ case s_http_host:+ if (s != s_http_host) {+ u->field_data[UF_HOST].off = p - buf;+ }+ u->field_data[UF_HOST].len++;+ break;++ case s_http_host_v6:+ if (s != s_http_host_v6) {+ u->field_data[UF_HOST].off = p - buf;+ }+ u->field_data[UF_HOST].len++;+ break;++ case s_http_host_port:+ if (s != s_http_host_port) {+ u->field_data[UF_PORT].off = p - buf;+ u->field_data[UF_PORT].len = 0;+ u->field_set |= (1 << UF_PORT);+ }+ u->field_data[UF_PORT].len++;+ break;++ case s_http_userinfo:+ if (s != s_http_userinfo) {+ u->field_data[UF_USERINFO].off = p - buf ;+ u->field_data[UF_USERINFO].len = 0;+ u->field_set |= (1 << UF_USERINFO);+ }+ u->field_data[UF_USERINFO].len++;+ break;++ default:+ break;+ }+ s = new_s;+ }++ /* Make sure we don't end somewhere unexpected */+ switch (s) {+ case s_http_host_start:+ case s_http_host_v6_start:+ case s_http_host_v6:+ case s_http_host_port_start:+ case s_http_userinfo:+ case s_http_userinfo_start:+ return 1;+ default:+ break;+ }++ return 0;+}++int+http_parser_parse_url(const char *buf, size_t buflen, int is_connect,+ struct http_parser_url *u)+{+ enum state s;+ const char *p;+ enum http_parser_url_fields uf, old_uf;+ int found_at = 0;++ u->port = u->field_set = 0;+ s = is_connect ? s_req_server_start : s_req_spaces_before_url;+ uf = old_uf = UF_MAX;++ for (p = buf; p < buf + buflen; p++) {+ s = parse_url_char(s, *p);++ /* Figure out the next field that we're operating on */+ switch (s) {+ case s_dead:+ return 1;++ /* Skip delimeters */+ case s_req_schema_slash:+ case s_req_schema_slash_slash:+ case s_req_server_start:+ case s_req_query_string_start:+ case s_req_fragment_start:+ continue;++ case s_req_schema:+ uf = UF_SCHEMA;+ break;++ case s_req_server_with_at:+ found_at = 1;++ /* FALLTROUGH */+ case s_req_server:+ uf = UF_HOST;+ break;++ case s_req_path:+ uf = UF_PATH;+ break;++ case s_req_query_string:+ uf = UF_QUERY;+ break;++ case s_req_fragment:+ uf = UF_FRAGMENT;+ break;++ default:+ assert(!"Unexpected state");+ return 1;+ }++ /* Nothing's changed; soldier on */+ if (uf == old_uf) {+ u->field_data[uf].len++;+ continue;+ }++ u->field_data[uf].off = p - buf;+ u->field_data[uf].len = 1;++ u->field_set |= (1 << uf);+ old_uf = uf;+ }++ /* host must be present if there is a schema */+ /* parsing http:///toto will fail */+ if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {+ if (http_parse_host(buf, u, found_at) != 0) {+ return 1;+ }+ }++ /* CONNECT requests can only contain "hostname:port" */+ if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {+ return 1;+ }++ if (u->field_set & (1 << UF_PORT)) {+ /* Don't bother with endp; we've already validated the string */+ unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);++ /* Ports have a max value of 2^16 */+ if (v > 0xffff) {+ return 1;+ }++ u->port = (uint16_t) v;+ }++ return 0;+}++void+http_parser_pause(http_parser *parser, int paused) {+ /* Users should only be pausing/unpausing a parser that is not in an error+ * state. In non-debug builds, there's not much that we can do about this+ * other than ignore it.+ */+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||+ HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {+ SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);+ } else {+ assert(0 && "Attempting to pause parser in error state");+ }+}++int+http_body_is_final(const struct http_parser *parser) {+ return parser->state == s_message_done; }
libgit2/deps/http-parser/http_parser.h view
@@ -24,16 +24,25 @@ extern "C" { #endif -#define HTTP_PARSER_VERSION_MAJOR 1+#define HTTP_PARSER_VERSION_MAJOR 2 #define HTTP_PARSER_VERSION_MINOR 0 -#ifdef _MSC_VER- /* disable silly warnings */-# pragma warning(disable: 4127 4214)-#endif- #include <sys/types.h>-#include "git2/common.h"+#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)+#include <BaseTsd.h>+typedef __int8 int8_t;+typedef unsigned __int8 uint8_t;+typedef __int16 int16_t;+typedef unsigned __int16 uint16_t;+typedef __int32 int32_t;+typedef unsigned __int32 uint32_t;+typedef __int64 int64_t;+typedef unsigned __int64 uint64_t;+typedef SIZE_T size_t;+typedef SSIZE_T ssize_t;+#else+#include <stdint.h>+#endif /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run * faster@@ -42,21 +51,12 @@ # define HTTP_PARSER_STRICT 1 #endif -/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to- * the error reporting facility.- */-#ifndef HTTP_PARSER_DEBUG-# define HTTP_PARSER_DEBUG 0-#endif-- /* Maximium header size allowed */ #define HTTP_MAX_HEADER_SIZE (80*1024) typedef struct http_parser http_parser; typedef struct http_parser_settings http_parser_settings;-typedef struct http_parser_result http_parser_result; /* Callbacks should return non-zero to indicate an error. The parser will@@ -69,7 +69,7 @@ * chunked' headers that indicate the presence of a body. * * http_data_cb does not return data chunks. It will be call arbitrarally- * many times for each string. E.G. you might get 10 callbacks for "on_path"+ * many times for each string. E.G. you might get 10 callbacks for "on_url" * each providing just a few characters more data. */ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);@@ -77,36 +77,44 @@ /* Request Methods */+#define HTTP_METHOD_MAP(XX) \+ XX(0, DELETE, DELETE) \+ XX(1, GET, GET) \+ XX(2, HEAD, HEAD) \+ XX(3, POST, POST) \+ XX(4, PUT, PUT) \+ /* pathological */ \+ XX(5, CONNECT, CONNECT) \+ XX(6, OPTIONS, OPTIONS) \+ XX(7, TRACE, TRACE) \+ /* webdav */ \+ XX(8, COPY, COPY) \+ XX(9, LOCK, LOCK) \+ XX(10, MKCOL, MKCOL) \+ XX(11, MOVE, MOVE) \+ XX(12, PROPFIND, PROPFIND) \+ XX(13, PROPPATCH, PROPPATCH) \+ XX(14, SEARCH, SEARCH) \+ XX(15, UNLOCK, UNLOCK) \+ /* subversion */ \+ XX(16, REPORT, REPORT) \+ XX(17, MKACTIVITY, MKACTIVITY) \+ XX(18, CHECKOUT, CHECKOUT) \+ XX(19, MERGE, MERGE) \+ /* upnp */ \+ XX(20, MSEARCH, M-SEARCH) \+ XX(21, NOTIFY, NOTIFY) \+ XX(22, SUBSCRIBE, SUBSCRIBE) \+ XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \+ /* RFC-5789 */ \+ XX(24, PATCH, PATCH) \+ XX(25, PURGE, PURGE) \+ enum http_method- { HTTP_DELETE = 0- , HTTP_GET- , HTTP_HEAD- , HTTP_POST- , HTTP_PUT- /* pathological */- , HTTP_CONNECT- , HTTP_OPTIONS- , HTTP_TRACE- /* webdav */- , HTTP_COPY- , HTTP_LOCK- , HTTP_MKCOL- , HTTP_MOVE- , HTTP_PROPFIND- , HTTP_PROPPATCH- , HTTP_UNLOCK- /* subversion */- , HTTP_REPORT- , HTTP_MKACTIVITY- , HTTP_CHECKOUT- , HTTP_MERGE- /* upnp */- , HTTP_MSEARCH- , HTTP_NOTIFY- , HTTP_SUBSCRIBE- , HTTP_UNSUBSCRIBE- /* RFC-5789 */- , HTTP_PATCH+ {+#define XX(num, name, string) HTTP_##name = num,+ HTTP_METHOD_MAP(XX)+#undef XX }; @@ -134,10 +142,7 @@ \ /* Callback-related errors */ \ XX(CB_message_begin, "the on_message_begin callback failed") \- XX(CB_path, "the on_path callback failed") \- XX(CB_query_string, "the on_query_string callback failed") \ XX(CB_url, "the on_url callback failed") \- XX(CB_fragment, "the on_fragment callback failed") \ XX(CB_header_field, "the on_header_field callback failed") \ XX(CB_header_value, "the on_header_value callback failed") \ XX(CB_headers_complete, "the on_headers_complete callback failed") \@@ -168,6 +173,7 @@ XX(INVALID_CONSTANT, "invalid constant string") \ XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ XX(STRICT, "strict mode assertion failed") \+ XX(PAUSED, "parser is paused") \ XX(UNKNOWN, "an unknown error occurred") @@ -182,30 +188,23 @@ /* Get an http_errno value from an http_parser */ #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) -/* Get the line number that generated the current error */-#if HTTP_PARSER_DEBUG-#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno)-#else-#define HTTP_PARSER_ERRNO_LINE(p) 0-#endif - struct http_parser { /** PRIVATE **/- unsigned char type : 2;- unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */- unsigned char state;- unsigned char header_state;- unsigned char index;+ unsigned char type : 2; /* enum http_parser_type */+ unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */+ unsigned char state; /* enum state from http_parser.c */+ unsigned char header_state; /* enum header_state from http_parser.c */+ unsigned char index; /* index into current matcher */ - size_t nread;- int64_t content_length;+ uint32_t nread; /* # bytes read in various scenarios */+ uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ /** READ-ONLY **/ unsigned short http_major; unsigned short http_minor; unsigned short status_code; /* responses only */- unsigned char method; /* requests only */+ unsigned char method; /* requests only */ unsigned char http_errno : 7; /* 1 = Upgrade header was present and the parser has exited because of that.@@ -215,10 +214,6 @@ */ unsigned char upgrade : 1; -#if HTTP_PARSER_DEBUG- uint32_t error_lineno;-#endif- /** PUBLIC **/ void *data; /* A pointer to get hook to the "connection" or "socket" object */ };@@ -235,6 +230,36 @@ }; +enum http_parser_url_fields+ { UF_SCHEMA = 0+ , UF_HOST = 1+ , UF_PORT = 2+ , UF_PATH = 3+ , UF_QUERY = 4+ , UF_FRAGMENT = 5+ , UF_USERINFO = 6+ , UF_MAX = 7+ };+++/* Result structure for http_parser_parse_url().+ *+ * Callers should index into field_data[] with UF_* values iff field_set+ * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and+ * because we probably have padding left over), we convert any port to+ * a uint16_t.+ */+struct http_parser_url {+ uint16_t field_set; /* Bitmask of (1 << UF_*) values */+ uint16_t port; /* Converted UF_PORT string */++ struct {+ uint16_t off; /* Offset into buffer in which field starts */+ uint16_t len; /* Length of run in buffer */+ } field_data[UF_MAX];+};++ void http_parser_init(http_parser *parser, enum http_parser_type type); @@ -245,12 +270,12 @@ /* If http_should_keep_alive() in the on_headers_complete or- * on_message_complete callback returns true, then this will be should be+ * on_message_complete callback returns 0, then this should be * the last message on the connection. * If you are the server, respond with the "Connection: close" header. * If you are the client, close the connection. */-int http_should_keep_alive(http_parser *parser);+int http_should_keep_alive(const http_parser *parser); /* Returns a string version of the HTTP method. */ const char *http_method_str(enum http_method m);@@ -260,6 +285,17 @@ /* Return a string description of the given error */ const char *http_errno_description(enum http_errno err);++/* Parse a URL; return nonzero on failure */+int http_parser_parse_url(const char *buf, size_t buflen,+ int is_connect,+ struct http_parser_url *u);++/* Pause or un-pause the parser; a nonzero value pauses */+void http_parser_pause(http_parser *parser, int paused);++/* Checks if this is the final chunk of the body. */+int http_body_is_final(const http_parser *parser); #ifdef __cplusplus }
libgit2/include/git2.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -23,8 +23,10 @@ #include "git2/repository.h" #include "git2/revwalk.h" #include "git2/merge.h"+#include "git2/graph.h" #include "git2/refs.h" #include "git2/reflog.h"+#include "git2/revparse.h" #include "git2/object.h" #include "git2/blob.h"@@ -35,13 +37,24 @@ #include "git2/index.h" #include "git2/config.h"+#include "git2/transport.h" #include "git2/remote.h"+#include "git2/clone.h"+#include "git2/checkout.h"+#include "git2/push.h" +#include "git2/attr.h"+#include "git2/ignore.h"+#include "git2/branch.h" #include "git2/refspec.h" #include "git2/net.h" #include "git2/status.h" #include "git2/indexer.h" #include "git2/submodule.h" #include "git2/notes.h"+#include "git2/reset.h"+#include "git2/message.h"+#include "git2/pack.h"+#include "git2/stash.h" #endif
libgit2/include/git2/attr.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -30,7 +30,7 @@ * Then for file `xyz.c` looking up attribute "foo" gives a value for * which `GIT_ATTR_TRUE(value)` is true. */-#define GIT_ATTR_TRUE(attr) ((attr) == git_attr__true)+#define GIT_ATTR_TRUE(attr) (git_attr_value(attr) == GIT_ATTR_TRUE_T) /** * GIT_ATTR_FALSE checks if an attribute is set off. In core git@@ -44,7 +44,7 @@ * Then for file `zyx.h` looking up attribute "foo" gives a value for * which `GIT_ATTR_FALSE(value)` is true. */-#define GIT_ATTR_FALSE(attr) ((attr) == git_attr__false)+#define GIT_ATTR_FALSE(attr) (git_attr_value(attr) == GIT_ATTR_FALSE_T) /** * GIT_ATTR_UNSPECIFIED checks if an attribute is unspecified. This@@ -62,11 +62,11 @@ * file `onefile.rb` or looking up "bar" on any file will all give * `GIT_ATTR_UNSPECIFIED(value)` of true. */-#define GIT_ATTR_UNSPECIFIED(attr) (!(attr) || (attr) == git_attr__unset)+#define GIT_ATTR_UNSPECIFIED(attr) (git_attr_value(attr) == GIT_ATTR_UNSPECIFIED_T) /** * GIT_ATTR_HAS_VALUE checks if an attribute is set to a value (as- * opposied to TRUE, FALSE or UNSPECIFIED). This would be the case if+ * opposed to TRUE, FALSE or UNSPECIFIED). This would be the case if * for a file with something like: * * *.txt eol=lf@@ -74,14 +74,30 @@ * Given this, looking up "eol" for `onefile.txt` will give back the * string "lf" and `GIT_ATTR_SET_TO_VALUE(attr)` will return true. */-#define GIT_ATTR_HAS_VALUE(attr) \- ((attr) && (attr) != git_attr__unset && \- (attr) != git_attr__true && (attr) != git_attr__false)+#define GIT_ATTR_HAS_VALUE(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_T) -GIT_EXTERN(const char *) git_attr__true;-GIT_EXTERN(const char *) git_attr__false;-GIT_EXTERN(const char *) git_attr__unset;+typedef enum {+ GIT_ATTR_UNSPECIFIED_T = 0,+ GIT_ATTR_TRUE_T,+ GIT_ATTR_FALSE_T,+ GIT_ATTR_VALUE_T,+} git_attr_t; +/*+ * Return the value type for a given attribute.+ *+ * This can be either `TRUE`, `FALSE`, `UNSPECIFIED` (if the attribute+ * was not set at all), or `VALUE`, if the attribute was set to+ * an actual string.+ *+ * If the attribute has a `VALUE` string, it can be accessed normally+ * as a NULL-terminated C string.+ *+ * @param attr The attribute+ * @return the value type for the attribute+ */+GIT_EXTERN(git_attr_t) git_attr_value(const char *attr);+ /** * Check attribute flags: Reading values from index and working directory. *@@ -167,29 +183,30 @@ size_t num_attr, const char **names); +typedef int (*git_attr_foreach_cb)(const char *name, const char *value, void *payload);+ /** * Loop over all the git attributes for a path. * * @param repo The repository containing the path. * @param flags A combination of GIT_ATTR_CHECK... flags.- * @param path The path inside the repo to check attributes. This- * does not have to exist, but if it does not, then- * it will be treated as a plain file (i.e. not a directory).- * @param callback The function that will be invoked on each attribute- * and attribute value. The name parameter will be the name- * of the attribute and the value will be the value it is- * set to, including possibly NULL if the attribute is- * explicitly set to UNSPECIFIED using the ! sign. This- * will be invoked only once per attribute name, even if- * there are multiple rules for a given file. The highest- * priority rule will be used.+ * @param path Path inside the repo to check attributes. This does not have+ * to exist, but if it does not, then it will be treated as a+ * plain file (i.e. not a directory).+ * @param callback Function to invoke on each attribute name and value. The+ * value may be NULL is the attribute is explicitly set to+ * UNSPECIFIED using the '!' sign. Callback will be invoked+ * only once per attribute name, even if there are multiple+ * rules for a given file. The highest priority rule will be+ * used. Return a non-zero value from this to stop looping. * @param payload Passed on as extra parameter to callback function.+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_attr_foreach( git_repository *repo, uint32_t flags, const char *path,- int (*callback)(const char *name, const char *value, void *payload),+ git_attr_foreach_cb callback, void *payload); /**
libgit2/include/git2/blob.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -46,7 +46,7 @@ * @param len the length of the short identifier * @return 0 or an error code */-GIT_INLINE(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, const git_oid *id, unsigned int len)+GIT_INLINE(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, const git_oid *id, size_t len) { return git_object_lookup_prefix((git_object **)blob, repo, id, len, GIT_OBJ_BLOB); }@@ -68,7 +68,18 @@ git_object_free((git_object *) blob); } +/**+ * Get the id of a blob.+ *+ * @param blob a previously loaded blob.+ * @return SHA1 hash for this blob.+ */+GIT_INLINE(const git_oid *) git_blob_id(const git_blob *blob)+{+ return git_object_id((const git_object *)blob);+} + /** * Get a read-only buffer with the raw content of a blob. *@@ -80,7 +91,7 @@ * @param blob pointer to the blob * @return the pointer; NULL if the blob has no contents */-GIT_EXTERN(const void *) git_blob_rawcontent(git_blob *blob);+GIT_EXTERN(const void *) git_blob_rawcontent(const git_blob *blob); /** * Get the size in bytes of the contents of a blob@@ -88,35 +99,80 @@ * @param blob pointer to the blob * @return size on bytes */-GIT_EXTERN(size_t) git_blob_rawsize(git_blob *blob);+GIT_EXTERN(git_off_t) git_blob_rawsize(const git_blob *blob); /** * Read a file from the working folder of a repository * and write it to the Object Database as a loose blob *- * @param oid return the id of the written blob+ * @param id return the id of the written blob * @param repo repository where the blob will be written. * this repository cannot be bare- * @param path file from which the blob will be created,+ * @param relative_path file from which the blob will be created, * relative to the repository's working dir * @return 0 or an error code */-GIT_EXTERN(int) git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path);+GIT_EXTERN(int) git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path); /** * Read a file from the filesystem and write its content * to the Object Database as a loose blob *- * @param oid return the id of the written blob+ * @param id return the id of the written blob * @param repo repository where the blob will be written. * this repository can be bare or not * @param path file from which the blob will be created * @return 0 or an error code */-GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path);+GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path); +typedef int (*git_blob_chunk_cb)(char *content, size_t max_length, void *payload);+ /**+ * Write a loose blob to the Object Database from a+ * provider of chunks of data.+ *+ * Provided the `hintpath` parameter is filled, its value+ * will help to determine what git filters should be applied+ * to the object before it can be placed to the object database.+ *+ *+ * The implementation of the callback has to respect the+ * following rules:+ *+ * - `content` will have to be filled by the consumer. The maximum number+ * of bytes that the buffer can accept per call is defined by the+ * `max_length` parameter. Allocation and freeing of the buffer will be taken+ * care of by the function.+ *+ * - The callback is expected to return the number of bytes+ * that `content` have been filled with.+ *+ * - When there is no more data to stream, the callback should+ * return 0. This will prevent it from being invoked anymore.+ *+ * - When an error occurs, the callback should return -1.+ *+ *+ * @param id Return the id of the written blob+ *+ * @param repo repository where the blob will be written.+ * This repository can be bare or not.+ *+ * @param hintpath if not NULL, will help selecting the filters+ * to apply onto the content of the blob to be created.+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_blob_create_fromchunks(+ git_oid *id,+ git_repository *repo,+ const char *hintpath,+ git_blob_chunk_cb callback,+ void *payload);++/** * Write an in-memory buffer to the ODB as a blob * * @param oid return the oid of the written blob@@ -126,6 +182,19 @@ * @return 0 or an error code */ GIT_EXTERN(int) git_blob_create_frombuffer(git_oid *oid, git_repository *repo, const void *buffer, size_t len);++/**+ * Determine if the blob content is most certainly binary or not.+ *+ * The heuristic used to guess if a file is binary is taken from core git:+ * Searching for NUL bytes and looking for a reasonable ratio of printable+ * to non-printable characters among the first 4000 bytes.+ *+ * @param blob The blob which content should be analyzed+ * @return 1 if the content of the blob is detected+ * as binary; 0 otherwise.+ */+GIT_EXTERN(int) git_blob_is_binary(git_blob *blob); /** @} */ GIT_END_DECL
libgit2/include/git2/branch.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,6 +8,7 @@ #define INCLUDE_git_branch_h__ #include "common.h"+#include "oid.h" #include "types.h" /**@@ -26,61 +27,53 @@ * this target commit. If `force` is true and a reference * already exists with the given name, it'll be replaced. *- * @param oid_out Pointer where to store the OID of the target commit.+ * The returned reference must be freed by the user. *- * @param repo Repository where to store the branch.+ * The branch name will be checked for validity.+ * See `git_tag_create()` for rules about valid names. *+ * @param out Pointer where to store the underlying reference.+ * * @param branch_name Name for the branch; this name is * validated for consistency. It should also not conflict with * an already existing branch name. *- * @param target Object to which this branch should point. This object- * must belong to the given `repo` and can either be a git_commit or a- * git_tag. When a git_tag is being passed, it should be dereferencable- * to a git_commit which oid will be used as the target of the branch.+ * @param target Commit to which this branch should point. This object+ * must belong to the given `repo`. * * @param force Overwrite existing branch. *- * @return 0 or an error code.+ * @return 0, GIT_EINVALIDSPEC or an error code. * A proper reference is written in the refs/heads namespace * pointing to the provided target commit. */ GIT_EXTERN(int) git_branch_create(- git_oid *oid_out,- git_repository *repo,- const char *branch_name,- const git_object *target,- int force);+ git_reference **out,+ git_repository *repo,+ const char *branch_name,+ const git_commit *target,+ int force); /** * Delete an existing branch reference. *- * @param repo Repository where lives the branch.- *- * @param branch_name Name of the branch to be deleted;- * this name is validated for consistency.- *- * @param branch_type Type of the considered branch. This should- * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE.+ * If the branch is successfully deleted, the passed reference+ * object will be freed and invalidated. *- * @return 0 on success, GIT_ENOTFOUND if the branch- * doesn't exist or an error code.+ * @param branch A valid reference representing a branch+ * @return 0 on success, or an error code. */-GIT_EXTERN(int) git_branch_delete(- git_repository *repo,- const char *branch_name,- git_branch_t branch_type);+GIT_EXTERN(int) git_branch_delete(git_reference *branch); +typedef int (*git_branch_foreach_cb)(+ const char *branch_name,+ git_branch_t branch_type,+ void *payload);+ /**- * Fill a list with all the branches in the Repository- *- * The string array will be filled with the names of the- * matching branches; these values are owned by the user and- * should be free'd manually when no longer needed, using- * `git_strarray_free`.+ * Loop over all the branches and issue a callback for each one. *- * @param branch_names Pointer to a git_strarray structure- * where the branch names will be stored.+ * If the callback returns a non-zero value, this will stop looping. * * @param repo Repository where to find the branches. *@@ -88,34 +81,171 @@ * listing. Valid values are GIT_BRANCH_LOCAL, GIT_BRANCH_REMOTE * or a combination of the two. *- * @return 0 or an error code.+ * @param branch_cb Callback to invoke per found branch.+ *+ * @param payload Extra parameter to callback function.+ *+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */-GIT_EXTERN(int) git_branch_list(- git_strarray *branch_names,- git_repository *repo,- unsigned int list_flags);+GIT_EXTERN(int) git_branch_foreach(+ git_repository *repo,+ unsigned int list_flags,+ git_branch_foreach_cb branch_cb,+ void *payload); /**- * Move/rename an existing branch reference.+ * Move/rename an existing local branch reference. *- * @param repo Repository where lives the branch.+ * The new branch name will be checked for validity.+ * See `git_tag_create()` for rules about valid names. *- * @param old_branch_name Current name of the branch to be moved;- * this name is validated for consistency.+ * @param branch Current underlying reference of the branch. * * @param new_branch_name Target name of the branch once the move * is performed; this name is validated for consistency. * * @param force Overwrite existing branch. *- * @return 0 on success, GIT_ENOTFOUND if the branch- * doesn't exist or an error code.+ * @return 0 on success, GIT_EINVALIDSPEC or an error code. */ GIT_EXTERN(int) git_branch_move(- git_repository *repo,- const char *old_branch_name,- const char *new_branch_name,- int force);+ git_reference **out,+ git_reference *branch,+ const char *new_branch_name,+ int force);++/**+ * Lookup a branch by its name in a repository.+ *+ * The generated reference must be freed by the user.+ *+ * The branch name will be checked for validity.+ * See `git_tag_create()` for rules about valid names.+ *+ * @param out pointer to the looked-up branch reference+ *+ * @param repo the repository to look up the branch+ *+ * @param branch_name Name of the branch to be looked-up;+ * this name is validated for consistency.+ *+ * @param branch_type Type of the considered branch. This should+ * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE.+ *+ * @return 0 on success; GIT_ENOTFOUND when no matching branch+ * exists, GIT_EINVALIDSPEC, otherwise an error code.+ */+GIT_EXTERN(int) git_branch_lookup(+ git_reference **out,+ git_repository *repo,+ const char *branch_name,+ git_branch_t branch_type);++/**+ * Return the name of the given local or remote branch.+ *+ * The name of the branch matches the definition of the name+ * for git_branch_lookup. That is, if the returned name is given+ * to git_branch_lookup() then the reference is returned that+ * was given to this function.+ *+ * @param out where the pointer of branch name is stored;+ * this is valid as long as the ref is not freed.+ * @param ref the reference ideally pointing to a branch+ *+ * @return 0 on success; otherwise an error code (e.g., if the+ * ref is no local or remote branch).+ */+GIT_EXTERN(int) git_branch_name(const char **out,+ git_reference *ref);++/**+ * Return the reference supporting the remote tracking branch,+ * given a local branch reference.+ *+ * @param out Pointer where to store the retrieved+ * reference.+ *+ * @param branch Current underlying reference of the branch.+ *+ * @return 0 on success; GIT_ENOTFOUND when no remote tracking+ * reference exists, otherwise an error code.+ */+GIT_EXTERN(int) git_branch_upstream(+ git_reference **out,+ git_reference *branch);++/**+ * Set the upstream configuration for a given local branch+ *+ * @param branch the branch to configure+ *+ * @param upstream_name remote-tracking or local branch to set as+ * upstream. Pass NULL to unset.+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_branch_set_upstream(git_reference *branch, const char *upstream_name);++/**+ * Return the name of the reference supporting the remote tracking branch,+ * given the name of a local branch reference.+ *+ * @param tracking_branch_name_out The user-allocated buffer which will be+ * filled with the name of the reference. Pass NULL if you just want to+ * get the needed size of the name of the reference as the output value.+ *+ * @param buffer_size Size of the `out` buffer in bytes.+ *+ * @param repo the repository where the branches live+ *+ * @param canonical_branch_name name of the local branch.+ *+ * @return number of characters in the reference name+ * including the trailing NUL byte; GIT_ENOTFOUND when no remote tracking+ * reference exists, otherwise an error code.+ */+GIT_EXTERN(int) git_branch_upstream_name(+ char *tracking_branch_name_out,+ size_t buffer_size,+ git_repository *repo,+ const char *canonical_branch_name);++/**+ * Determine if the current local branch is pointed at by HEAD.+ *+ * @param branch Current underlying reference of the branch.+ *+ * @return 1 if HEAD points at the branch, 0 if it isn't,+ * error code otherwise.+ */+GIT_EXTERN(int) git_branch_is_head(+ git_reference *branch);++/**+ * Return the name of remote that the remote tracking branch belongs to.+ *+ * @param remote_name_out The user-allocated buffer which will be+ * filled with the name of the remote. Pass NULL if you just want to+ * get the needed size of the name of the remote as the output value.+ *+ * @param buffer_size Size of the `out` buffer in bytes.+ *+ * @param repo The repository where the branch lives.+ *+ * @param canonical_branch_name name of the remote tracking branch.+ *+ * @return Number of characters in the reference name+ * including the trailing NUL byte; GIT_ENOTFOUND+ * when no remote matching remote was gound,+ * GIT_EAMBIGUOUS when the branch maps to several remotes,+ * otherwise an error code.+ */+GIT_EXTERN(int) git_branch_remote_name(+ char *remote_name_out,+ size_t buffer_size,+ git_repository *repo,+ const char *canonical_branch_name); /** @} */ GIT_END_DECL
+ libgit2/include/git2/checkout.h view
@@ -0,0 +1,285 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_checkout_h__+#define INCLUDE_git_checkout_h__++#include "common.h"+#include "types.h"+#include "diff.h"++/**+ * @file git2/checkout.h+ * @brief Git checkout routines+ * @defgroup git_checkout Git checkout routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Checkout behavior flags+ *+ * In libgit2, checkout is used to update the working directory and index+ * to match a target tree. Unlike git checkout, it does not move the HEAD+ * commit for you - use `git_repository_set_head` or the like to do that.+ *+ * Checkout looks at (up to) four things: the "target" tree you want to+ * check out, the "baseline" tree of what was checked out previously, the+ * working directory for actual files, and the index for staged changes.+ *+ * You give checkout one of four strategies for update:+ *+ * - `GIT_CHECKOUT_NONE` is a dry-run strategy that checks for conflicts,+ * etc., but doesn't make any actual changes.+ *+ * - `GIT_CHECKOUT_FORCE` is at the opposite extreme, taking any action to+ * make the working directory match the target (including potentially+ * discarding modified files).+ *+ * In between those are `GIT_CHECKOUT_SAFE` and `GIT_CHECKOUT_SAFE_CREATE`+ * both of which only make modifications that will not lose changes.+ *+ * | target == baseline | target != baseline |+ * ---------------------|-----------------------|----------------------|+ * workdir == baseline | no action | create, update, or |+ * | | delete file |+ * ---------------------|-----------------------|----------------------|+ * workdir exists and | no action | conflict (notify |+ * is != baseline | notify dirty MODIFIED | and cancel checkout) |+ * ---------------------|-----------------------|----------------------|+ * workdir missing, | create if SAFE_CREATE | create file |+ * baseline present | notify dirty DELETED | |+ * ---------------------|-----------------------|----------------------|+ *+ * The only difference between SAFE and SAFE_CREATE is that SAFE_CREATE+ * will cause a file to be checked out if it is missing from the working+ * directory even if it is not modified between the target and baseline.+ *+ *+ * To emulate `git checkout`, use `GIT_CHECKOUT_SAFE` with a checkout+ * notification callback (see below) that displays information about dirty+ * files. The default behavior will cancel checkout on conflicts.+ *+ * To emulate `git checkout-index`, use `GIT_CHECKOUT_SAFE_CREATE` with a+ * notification callback that cancels the operation if a dirty-but-existing+ * file is found in the working directory. This core git command isn't+ * quite "force" but is sensitive about some types of changes.+ *+ * To emulate `git checkout -f`, use `GIT_CHECKOUT_FORCE`.+ *+ * To emulate `git clone` use `GIT_CHECKOUT_SAFE_CREATE` in the options.+ *+ *+ * There are some additional flags to modified the behavior of checkout:+ *+ * - GIT_CHECKOUT_ALLOW_CONFLICTS makes SAFE mode apply safe file updates+ * even if there are conflicts (instead of cancelling the checkout).+ *+ * - GIT_CHECKOUT_REMOVE_UNTRACKED means remove untracked files (i.e. not+ * in target, baseline, or index, and not ignored) from the working dir.+ *+ * - GIT_CHECKOUT_REMOVE_IGNORED means remove ignored files (that are also+ * untracked) from the working directory as well.+ *+ * - GIT_CHECKOUT_UPDATE_ONLY means to only update the content of files that+ * already exist. Files will not be created nor deleted. This just skips+ * applying adds, deletes, and typechanges.+ *+ * - GIT_CHECKOUT_DONT_UPDATE_INDEX prevents checkout from writing the+ * updated files' information to the index.+ *+ * - Normally, checkout will reload the index and git attributes from disk+ * before any operations. GIT_CHECKOUT_NO_REFRESH prevents this reload.+ *+ * - Unmerged index entries are conflicts. GIT_CHECKOUT_SKIP_UNMERGED skips+ * files with unmerged index entries instead. GIT_CHECKOUT_USE_OURS and+ * GIT_CHECKOUT_USE_THEIRS to proceed with the checkout using either the+ * stage 2 ("ours") or stage 3 ("theirs") version of files in the index.+ */+typedef enum {+ GIT_CHECKOUT_NONE = 0, /** default is a dry run, no actual updates */++ /** Allow safe updates that cannot overwrite uncommitted data */+ GIT_CHECKOUT_SAFE = (1u << 0),++ /** Allow safe updates plus creation of missing files */+ GIT_CHECKOUT_SAFE_CREATE = (1u << 1),++ /** Allow all updates to force working directory to look like index */+ GIT_CHECKOUT_FORCE = (1u << 2),+++ /** Allow checkout to make safe updates even if conflicts are found */+ GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),++ /** Remove untracked files not in index (that are not ignored) */+ GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),++ /** Remove ignored files not in index */+ GIT_CHECKOUT_REMOVE_IGNORED = (1u << 6),++ /** Only update existing files, don't create new ones */+ GIT_CHECKOUT_UPDATE_ONLY = (1u << 7),++ /** Normally checkout updates index entries as it goes; this stops that */+ GIT_CHECKOUT_DONT_UPDATE_INDEX = (1u << 8),++ /** Don't refresh index/config/etc before doing checkout */+ GIT_CHECKOUT_NO_REFRESH = (1u << 9),++ /** Treat pathspec as simple list of exact match file paths */+ GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1u << 13),++ /**+ * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED+ */++ /** Allow checkout to skip unmerged files (NOT IMPLEMENTED) */+ GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),+ /** For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) */+ GIT_CHECKOUT_USE_OURS = (1u << 11),+ /** For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) */+ GIT_CHECKOUT_USE_THEIRS = (1u << 12),++ /** Recursively checkout submodules with same options (NOT IMPLEMENTED) */+ GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),+ /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */+ GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17),++} git_checkout_strategy_t;++/**+ * Checkout notification flags+ *+ * Checkout will invoke an options notification callback (`notify_cb`) for+ * certain cases - you pick which ones via `notify_flags`:+ *+ * - GIT_CHECKOUT_NOTIFY_CONFLICT invokes checkout on conflicting paths.+ *+ * - GIT_CHECKOUT_NOTIFY_DIRTY notifies about "dirty" files, i.e. those that+ * do not need an update but no longer match the baseline. Core git+ * displays these files when checkout runs, but won't stop the checkout.+ *+ * - GIT_CHECKOUT_NOTIFY_UPDATED sends notification for any file changed.+ *+ * - GIT_CHECKOUT_NOTIFY_UNTRACKED notifies about untracked files.+ *+ * - GIT_CHECKOUT_NOTIFY_IGNORED notifies about ignored files.+ *+ * Returning a non-zero value from this callback will cancel the checkout.+ * Notification callbacks are made prior to modifying any files on disk.+ */+typedef enum {+ GIT_CHECKOUT_NOTIFY_NONE = 0,+ GIT_CHECKOUT_NOTIFY_CONFLICT = (1u << 0),+ GIT_CHECKOUT_NOTIFY_DIRTY = (1u << 1),+ GIT_CHECKOUT_NOTIFY_UPDATED = (1u << 2),+ GIT_CHECKOUT_NOTIFY_UNTRACKED = (1u << 3),+ GIT_CHECKOUT_NOTIFY_IGNORED = (1u << 4),+} git_checkout_notify_t;++/** Checkout notification callback function */+typedef int (*git_checkout_notify_cb)(+ git_checkout_notify_t why,+ const char *path,+ const git_diff_file *baseline,+ const git_diff_file *target,+ const git_diff_file *workdir,+ void *payload);++/** Checkout progress notification function */+typedef void (*git_checkout_progress_cb)(+ const char *path,+ size_t completed_steps,+ size_t total_steps,+ void *payload);++/**+ * Checkout options structure+ *+ * Zero out for defaults. Initialize with `GIT_CHECKOUT_OPTS_INIT` macro to+ * correctly set the `version` field. E.g.+ *+ * git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;+ */+typedef struct git_checkout_opts {+ unsigned int version;++ unsigned int checkout_strategy; /** default will be a dry run */++ int disable_filters; /** don't apply filters like CRLF conversion */+ unsigned int dir_mode; /** default is 0755 */+ unsigned int file_mode; /** default is 0644 or 0755 as dictated by blob */+ int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */++ unsigned int notify_flags; /** see `git_checkout_notify_t` above */+ git_checkout_notify_cb notify_cb;+ void *notify_payload;++ /* Optional callback to notify the consumer of checkout progress. */+ git_checkout_progress_cb progress_cb;+ void *progress_payload;++ /** When not zeroed out, array of fnmatch patterns specifying which+ * paths should be taken into account, otherwise all files. Use+ * GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH to treat as simple list.+ */+ git_strarray paths;++ git_tree *baseline; /** expected content of workdir, defaults to HEAD */+} git_checkout_opts;++#define GIT_CHECKOUT_OPTS_VERSION 1+#define GIT_CHECKOUT_OPTS_INIT {GIT_CHECKOUT_OPTS_VERSION}++/**+ * Updates files in the index and the working tree to match the content of+ * the commit pointed at by HEAD.+ *+ * @param repo repository to check out (must be non-bare)+ * @param opts specifies checkout options (may be NULL)+ * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing+ * branch, GIT_ERROR otherwise (use giterr_last for information+ * about the error)+ */+GIT_EXTERN(int) git_checkout_head(+ git_repository *repo,+ git_checkout_opts *opts);++/**+ * Updates files in the working tree to match the content of the index.+ *+ * @param repo repository into which to check out (must be non-bare)+ * @param index index to be checked out (or NULL to use repository index)+ * @param opts specifies checkout options (may be NULL)+ * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information+ * about the error)+ */+GIT_EXTERN(int) git_checkout_index(+ git_repository *repo,+ git_index *index,+ git_checkout_opts *opts);++/**+ * Updates files in the index and working tree to match the content of the+ * tree pointed at by the treeish.+ *+ * @param repo repository to check out (must be non-bare)+ * @param treeish a commit, tag or tree which content will be used to update+ * the working directory+ * @param opts specifies checkout options (may be NULL)+ * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information+ * about the error)+ */+GIT_EXTERN(int) git_checkout_tree(+ git_repository *repo,+ const git_object *treeish,+ git_checkout_opts *opts);++/** @} */+GIT_END_DECL+#endif
+ libgit2/include/git2/clone.h view
@@ -0,0 +1,107 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_clone_h__+#define INCLUDE_git_clone_h__++#include "common.h"+#include "types.h"+#include "indexer.h"+#include "checkout.h"+#include "remote.h"+++/**+ * @file git2/clone.h+ * @brief Git cloning routines+ * @defgroup git_clone Git cloning routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Clone options structure+ *+ * Use zeros to indicate default settings. It's easiest to use the+ * `GIT_CLONE_OPTIONS_INIT` macro:+ *+ * git_clone_options opts = GIT_CLONE_OPTIONS_INIT;+ *+ * - `checkout_opts` is options for the checkout step. To disable checkout,+ * set the `checkout_strategy` to GIT_CHECKOUT_DEFAULT.+ * - `bare` should be set to zero to create a standard repo, non-zero for+ * a bare repo+ * - `fetch_progress_cb` is optional callback for fetch progress. Be aware that+ * this is called inline with network and indexing operations, so performance+ * may be affected.+ * - `fetch_progress_payload` is payload for fetch_progress_cb+ *+ * ** "origin" remote options: **+ * - `remote_name` is the name given to the "origin" remote. The default is+ * "origin".+ * - `pushurl` is a URL to be used for pushing. NULL means use the fetch url.+ * - `fetch_spec` is the fetch specification to be used for fetching. NULL+ * results in the same behavior as GIT_REMOTE_DEFAULT_FETCH.+ * - `push_spec` is the fetch specification to be used for pushing. NULL means+ * use the same spec as for fetching.+ * - `cred_acquire_cb` is a callback to be used if credentials are required+ * during the initial fetch.+ * - `cred_acquire_payload` is the payload for the above callback.+ * - `transport` is a custom transport to be used for the initial fetch. NULL+ * means use the transport autodetected from the URL.+ * - `remote_callbacks` may be used to specify custom progress callbacks for+ * the origin remote before the fetch is initiated.+ * - `remote_autotag` may be used to specify the autotag setting before the+ * initial fetch. The default is GIT_REMOTE_DOWNLOAD_TAGS_ALL.+ * - `checkout_branch` gives the name of the branch to checkout. NULL means+ * use the remote's HEAD.+ */++typedef struct git_clone_options {+ unsigned int version;++ git_checkout_opts checkout_opts;+ int bare;+ git_transfer_progress_callback fetch_progress_cb;+ void *fetch_progress_payload;++ const char *remote_name;+ const char *pushurl;+ const char *fetch_spec;+ const char *push_spec;+ git_cred_acquire_cb cred_acquire_cb;+ void *cred_acquire_payload;+ git_transport *transport;+ git_remote_callbacks *remote_callbacks;+ git_remote_autotag_option_t remote_autotag;+ const char* checkout_branch;+} git_clone_options;++#define GIT_CLONE_OPTIONS_VERSION 1+#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTS_VERSION, GIT_CHECKOUT_SAFE_CREATE}}++/**+ * Clone a remote repository, and checkout the branch pointed to by the remote+ * HEAD.+ *+ * @param out pointer that will receive the resulting repository object+ * @param url the remote repository to clone+ * @param local_path local directory to clone to+ * @param options configuration options for the clone. If NULL, the function+ * works as though GIT_OPTIONS_INIT were passed.+ * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information+ * about the error)+ */+GIT_EXTERN(int) git_clone(+ git_repository **out,+ const char *url,+ const char *local_path,+ const git_clone_options *options);++/** @} */+GIT_END_DECL+#endif
libgit2/include/git2/commit.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -48,7 +48,7 @@ * @param len the length of the short identifier * @return 0 or an error code */-GIT_INLINE(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, unsigned len)+GIT_INLINE(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, size_t len) { return git_object_lookup_prefix((git_object **)commit, repo, id, len, GIT_OBJ_COMMIT); }@@ -76,7 +76,10 @@ * @param commit a previously loaded commit. * @return object identity for the commit. */-GIT_EXTERN(const git_oid *) git_commit_id(git_commit *commit);+GIT_INLINE(const git_oid *) git_commit_id(const git_commit *commit)+{+ return git_object_id((const git_object *)commit);+} /** * Get the encoding for the message of a commit,@@ -88,7 +91,7 @@ * @param commit a previously loaded commit. * @return NULL, or the encoding */-GIT_EXTERN(const char *) git_commit_message_encoding(git_commit *commit);+GIT_EXTERN(const char *) git_commit_message_encoding(const git_commit *commit); /** * Get the full message of a commit.@@ -96,7 +99,7 @@ * @param commit a previously loaded commit. * @return the message of a commit */-GIT_EXTERN(const char *) git_commit_message(git_commit *commit);+GIT_EXTERN(const char *) git_commit_message(const git_commit *commit); /** * Get the commit time (i.e. committer time) of a commit.@@ -104,7 +107,7 @@ * @param commit a previously loaded commit. * @return the time of a commit */-GIT_EXTERN(git_time_t) git_commit_time(git_commit *commit);+GIT_EXTERN(git_time_t) git_commit_time(const git_commit *commit); /** * Get the commit timezone offset (i.e. committer's preferred timezone) of a commit.@@ -112,7 +115,7 @@ * @param commit a previously loaded commit. * @return positive or negative timezone offset, in minutes from UTC */-GIT_EXTERN(int) git_commit_time_offset(git_commit *commit);+GIT_EXTERN(int) git_commit_time_offset(const git_commit *commit); /** * Get the committer of a commit.@@ -120,7 +123,7 @@ * @param commit a previously loaded commit. * @return the committer of a commit */-GIT_EXTERN(const git_signature *) git_commit_committer(git_commit *commit);+GIT_EXTERN(const git_signature *) git_commit_committer(const git_commit *commit); /** * Get the author of a commit.@@ -128,7 +131,7 @@ * @param commit a previously loaded commit. * @return the author of a commit */-GIT_EXTERN(const git_signature *) git_commit_author(git_commit *commit);+GIT_EXTERN(const git_signature *) git_commit_author(const git_commit *commit); /** * Get the tree pointed to by a commit.@@ -137,7 +140,7 @@ * @param commit a previously loaded commit. * @return 0 or an error code */-GIT_EXTERN(int) git_commit_tree(git_tree **tree_out, git_commit *commit);+GIT_EXTERN(int) git_commit_tree(git_tree **tree_out, const git_commit *commit); /** * Get the id of the tree pointed to by a commit. This differs from@@ -147,7 +150,7 @@ * @param commit a previously loaded commit. * @return the id of tree pointed to by commit. */-GIT_EXTERN(const git_oid *) git_commit_tree_oid(git_commit *commit);+GIT_EXTERN(const git_oid *) git_commit_tree_id(const git_commit *commit); /** * Get the number of parents of this commit@@ -155,17 +158,17 @@ * @param commit a previously loaded commit. * @return integer of count of parents */-GIT_EXTERN(unsigned int) git_commit_parentcount(git_commit *commit);+GIT_EXTERN(unsigned int) git_commit_parentcount(const git_commit *commit); /** * Get the specified parent of the commit. *- * @param parent Pointer where to store the parent commit+ * @param out Pointer where to store the parent commit * @param commit a previously loaded commit. * @param n the position of the parent (from 0 to `parentcount`) * @return 0 or an error code */-GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n);+GIT_EXTERN(int) git_commit_parent(git_commit **out, git_commit *commit, unsigned int n); /** * Get the oid of a specified parent for a commit. This is different from@@ -176,16 +179,35 @@ * @param n the position of the parent (from 0 to `parentcount`) * @return the id of the parent, NULL on error. */-GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned int n);+GIT_EXTERN(const git_oid *) git_commit_parent_id(git_commit *commit, unsigned int n); /**+ * Get the commit object that is the <n>th generation ancestor+ * of the named commit object, following only the first parents.+ * The returned commit has to be freed by the caller.+ *+ * Passing `0` as the generation number returns another instance of the+ * base commit itself.+ *+ * @param ancestor Pointer where to store the ancestor commit+ * @param commit a previously loaded commit.+ * @param n the requested generation+ * @return 0 on success; GIT_ENOTFOUND if no matching ancestor exists+ * or an error code+ */+GIT_EXTERN(int) git_commit_nth_gen_ancestor(+ git_commit **ancestor,+ const git_commit *commit,+ unsigned int n);++/** * Create a new commit in the repository using `git_object` * instances as parameters. *- * The message will be cleaned up from excess whitespace- * it will be made sure that the last line ends with a '\n'.+ * The message will not be cleaned up. This can be achieved+ * through `git_message_prettify()`. *- * @param oid Pointer where to store the OID of the+ * @param id Pointer where to store the OID of the * newly created commit * * @param repo Repository where to store the commit@@ -226,7 +248,7 @@ * the given reference will be updated to point to it */ GIT_EXTERN(int) git_commit_create(- git_oid *oid,+ git_oid *id, git_repository *repo, const char *update_ref, const git_signature *author,@@ -254,7 +276,7 @@ * @see git_commit_create */ GIT_EXTERN(int) git_commit_create_v(- git_oid *oid,+ git_oid *id, git_repository *repo, const char *update_ref, const git_signature *author,
libgit2/include/git2/common.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -55,6 +55,10 @@ #define GIT_WIN32 1 #endif +#ifdef __amigaos4__+#include <netinet/in.h>+#endif+ /** * @file git2/common.h * @brief Git common platform definitions@@ -81,13 +85,10 @@ */ #define GIT_PATH_MAX 4096 -typedef struct {- char **strings;- size_t count;-} git_strarray;--GIT_EXTERN(void) git_strarray_free(git_strarray *array);-GIT_EXTERN(int) git_strarray_copy(git_strarray *tgt, const git_strarray *src);+/**+ * The string representation of the null object ID.+ */+#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000" /** * Return the version of the libgit2 library@@ -99,6 +100,91 @@ */ GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev); +/**+ * Combinations of these values describe the capabilities of libgit2.+ */+enum {+ GIT_CAP_THREADS = ( 1 << 0 ),+ GIT_CAP_HTTPS = ( 1 << 1 )+};++/**+ * Query compile time options for libgit2.+ *+ * @return A combination of GIT_CAP_* values.+ *+ * - GIT_CAP_THREADS+ * Libgit2 was compiled with thread support. Note that thread support is still to be seen as a+ * 'work in progress'.+ *+ * - GIT_CAP_HTTPS+ * Libgit2 supports the https:// protocol. This requires the open ssl library to be+ * found when compiling libgit2.+ */+GIT_EXTERN(int) git_libgit2_capabilities(void);+++enum {+ GIT_OPT_GET_MWINDOW_SIZE,+ GIT_OPT_SET_MWINDOW_SIZE,+ GIT_OPT_GET_MWINDOW_MAPPED_LIMIT,+ GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,+ GIT_OPT_GET_SEARCH_PATH,+ GIT_OPT_SET_SEARCH_PATH,+ GIT_OPT_GET_ODB_CACHE_SIZE,+ GIT_OPT_SET_ODB_CACHE_SIZE,+};++/**+ * Set or query a library global option+ *+ * Available options:+ *+ * opts(GIT_OPT_GET_MWINDOW_SIZE, size_t *):+ * Get the maximum mmap window size+ *+ * opts(GIT_OPT_SET_MWINDOW_SIZE, size_t):+ * Set the maximum mmap window size+ *+ * opts(GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, size_t *):+ * Get the maximum memory that will be mapped in total by the library+ *+ * opts(GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, size_t):+ * Set the maximum amount of memory that can be mapped at any time+ * by the library+ *+ * opts(GIT_OPT_GET_SEARCH_PATH, int level, char *out, size_t len)+ * Get the search path for a given level of config data. "level" must+ * be one of GIT_CONFIG_LEVEL_SYSTEM, GIT_CONFIG_LEVEL_GLOBAL, or+ * GIT_CONFIG_LEVEL_XDG. The search path is written to the `out`+ * buffer up to size `len`. Returns GIT_EBUFS if buffer is too small.+ *+ * opts(GIT_OPT_SET_SEARCH_PATH, int level, const char *path)+ * Set the search path for a level of config data. The search path+ * applied to shared attributes and ignore files, too.+ * - `path` lists directories delimited by GIT_PATH_LIST_SEPARATOR.+ * Pass NULL to reset to the default (generally based on environment+ * variables). Use magic path `$PATH` to include the old value+ * of the path (if you want to prepend or append, for instance).+ * - `level` must be GIT_CONFIG_LEVEL_SYSTEM, GIT_CONFIG_LEVEL_GLOBAL,+ * or GIT_CONFIG_LEVEL_XDG.+ *+ * opts(GIT_OPT_GET_ODB_CACHE_SIZE):+ * Get the size of the libgit2 odb cache.+ *+ * opts(GIT_OPT_SET_ODB_CACHE_SIZE):+ * Set the size of the of the libgit2 odb cache. This needs+ * to be done before git_repository_open is called, since+ * git_repository_open initializes the odb layer. Defaults+ * to 128.+ *+ * @param option Option key+ * @param ... value to set the option+ * @return 0 on success, <0 on failure+ */+GIT_EXTERN(int) git_libgit2_opts(int option, ...);+ /** @} */ GIT_END_DECL+ #endif
libgit2/include/git2/config.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -20,22 +20,51 @@ GIT_BEGIN_DECL /**+ * Priority level of a config file.+ * These priority levels correspond to the natural escalation logic+ * (from higher to lower) when searching for config entries in git.git.+ *+ * git_config_open_default() and git_repository_config() honor those+ * priority levels as well.+ */+enum {+ GIT_CONFIG_LEVEL_SYSTEM = 1, /**< System-wide configuration file. */+ GIT_CONFIG_LEVEL_XDG = 2, /**< XDG compatible configuration file (.config/git/config). */+ GIT_CONFIG_LEVEL_GLOBAL = 3, /**< User-specific configuration file, also called Global configuration file. */+ GIT_CONFIG_LEVEL_LOCAL = 4, /**< Repository specific configuration file. */+ GIT_CONFIG_HIGHEST_LEVEL = -1, /**< Represents the highest level of a config file. */+};++typedef struct {+ const char *name;+ const char *value;+ unsigned int level;+} git_config_entry;++typedef int (*git_config_foreach_cb)(const git_config_entry *, void *);+++/** * Generic backend that implements the interface to * access a configuration file */-struct git_config_file {+struct git_config_backend {+ unsigned int version; struct git_config *cfg; /* Open means open the file/database and parse if necessary */- int (*open)(struct git_config_file *);- int (*get)(struct git_config_file *, const char *key, const char **value);- int (*get_multivar)(struct git_config_file *, const char *key, const char *regexp, int (*fn)(const char *, void *), void *data);- int (*set)(struct git_config_file *, const char *key, const char *value);- int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value);- int (*del)(struct git_config_file *, const char *key);- int (*foreach)(struct git_config_file *, int (*fn)(const char *, const char *, void *), void *data);- void (*free)(struct git_config_file *);+ int (*open)(struct git_config_backend *, unsigned int level);+ int (*get)(const struct git_config_backend *, const char *key, const git_config_entry **entry);+ int (*get_multivar)(struct git_config_backend *, const char *key, const char *regexp, git_config_foreach_cb callback, void *payload);+ int (*set)(struct git_config_backend *, const char *key, const char *value);+ int (*set_multivar)(git_config_backend *cfg, const char *name, const char *regexp, const char *value);+ int (*del)(struct git_config_backend *, const char *key);+ int (*foreach)(struct git_config_backend *, const char *, git_config_foreach_cb callback, void *payload);+ int (*refresh)(struct git_config_backend *);+ void (*free)(struct git_config_backend *); };+#define GIT_CONFIG_BACKEND_VERSION 1+#define GIT_CONFIG_BACKEND_INIT {GIT_CONFIG_BACKEND_VERSION} typedef enum { GIT_CVAR_FALSE = 0,@@ -61,11 +90,32 @@ * may be used on any `git_config` call to load the * global configuration file. *- * @param global_config_path Buffer of GIT_PATH_MAX length to store the path- * @return 0 if a global configuration file has been+ * This method will not guess the path to the xdg compatible+ * config file (.config/git/config).+ *+ * @param out Buffer to store the path in+ * @param length size of the buffer in bytes+ * @return 0 if a global configuration file has been found. Its path will be stored in `buffer`.+ */+GIT_EXTERN(int) git_config_find_global(char *out, size_t length);++/**+ * Locate the path to the global xdg compatible configuration file+ *+ * The xdg compatible configuration file is usually+ * located in `$HOME/.config/git/config`.+ *+ * This method will try to guess the full path to that+ * file, if the file exists. The returned path+ * may be used on any `git_config` call to load the+ * xdg compatible configuration file.+ *+ * @param out Buffer to store the path in+ * @param length size of the buffer in bytes+ * @return 0 if a xdg compatible configuration file has been * found. Its path will be stored in `buffer`. */-GIT_EXTERN(int) git_config_find_global(char *global_config_path, size_t length);+GIT_EXTERN(int) git_config_find_xdg(char *out, size_t length); /** * Locate the path to the system configuration file@@ -73,35 +123,24 @@ * If /etc/gitconfig doesn't exist, it will look for * %PROGRAMFILES%\Git\etc\gitconfig. - * @param system_config_path Buffer of GIT_PATH_MAX length to store the path+ * @param global_config_path Buffer to store the path in+ * @param length size of the buffer in bytes * @return 0 if a system configuration file has been * found. Its path will be stored in `buffer`. */-GIT_EXTERN(int) git_config_find_system(char *system_config_path, size_t length);+GIT_EXTERN(int) git_config_find_system(char *out, size_t length); /**- * Open the global configuration file+ * Open the global, XDG and system configuration files *- * Utility wrapper that calls `git_config_find_global`- * and opens the located file, if it exists.+ * Utility wrapper that finds the global, XDG and system configuration files+ * and opens them into a single prioritized config object that can be+ * used when accessing default config data outside a repository. * * @param out Pointer to store the config instance * @return 0 or an error code */-GIT_EXTERN(int) git_config_open_global(git_config **out);--/**- * Create a configuration file backend for ondisk files- *- * These are the normal `.gitconfig` files that Core Git- * processes. Note that you first have to add this file to a- * configuration object before you can query it for configuration- * variables.- *- * @param out the new backend- * @param path where the config file is located- */-GIT_EXTERN(int) git_config_file__ondisk(struct git_config_file **out, const char *path);+GIT_EXTERN(int) git_config_open_default(git_config **out); /** * Allocate a new configuration object@@ -122,14 +161,21 @@ * * Further queries on this config object will access each * of the config file instances in order (instances with- * a higher priority will be accessed first).+ * a higher priority level will be accessed first). * * @param cfg the configuration to add the file to * @param file the configuration file (backend) to add- * @param priority the priority the backend should have- * @return 0 or an error code+ * @param level the priority level of the backend+ * @param force if a config file already exists for the given+ * priority level, replace it+ * @return 0 on success, GIT_EEXISTS when adding more than one file+ * for a given priority level (and force_replace set to 0), or error code */-GIT_EXTERN(int) git_config_add_file(git_config *cfg, git_config_file *file, int priority);+GIT_EXTERN(int) git_config_add_backend(+ git_config *cfg,+ git_config_backend *file,+ unsigned int level,+ int force); /** * Add an on-disk config file instance to an existing config@@ -143,15 +189,22 @@ * * Further queries on this config object will access each * of the config file instances in order (instances with- * a higher priority will be accessed first).+ * a higher priority level will be accessed first). * * @param cfg the configuration to add the file to * @param path path to the configuration file (backend) to add- * @param priority the priority the backend should have- * @return 0 or an error code+ * @param level the priority level of the backend+ * @param force if a config file already exists for the given+ * priority level, replace it+ * @return 0 on success, GIT_EEXISTS when adding more than one file+ * for a given priority level (and force_replace set to 0),+ * GIT_ENOTFOUND when the file doesn't exist or error code */-GIT_EXTERN(int) git_config_add_file_ondisk(git_config *cfg, const char *path, int priority);-+GIT_EXTERN(int) git_config_add_file_ondisk(+ git_config *cfg,+ const char *path,+ unsigned int level,+ int force); /** * Create a new config instance containing a single on-disk file@@ -161,11 +214,46 @@ * - git_config_new * - git_config_add_file_ondisk *- * @param cfg The configuration instance to create+ * @param out The configuration instance to create * @param path Path to the on-disk file to open+ * @return 0 on success, GIT_ENOTFOUND when the file doesn't exist+ * or an error code+ */+GIT_EXTERN(int) git_config_open_ondisk(git_config **out, const char *path);++/**+ * Build a single-level focused config object from a multi-level one.+ *+ * The returned config object can be used to perform get/set/delete operations+ * on a single specific level.+ *+ * Getting several times the same level from the same parent multi-level config+ * will return different config instances, but containing the same config_file+ * instance.+ *+ * @param out The configuration instance to create+ * @param parent Multi-level config to search for the given level+ * @param level Configuration level to search for+ * @return 0, GIT_ENOTFOUND if the passed level cannot be found in the+ * multi-level parent config, or an error code+ */+GIT_EXTERN(int) git_config_open_level(+ git_config **out,+ const git_config *parent,+ unsigned int level);++/**+ * Reload changed config files+ *+ * A config file may be changed on disk out from under the in-memory+ * config object. This function causes us to look for files that have+ * been modified since we last loaded them and refresh the config with+ * the latest information.+ *+ * @param cfg The configuration to refresh * @return 0 or an error code */-GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path);+GIT_EXTERN(int) git_config_refresh(git_config *cfg); /** * Free the configuration and its associated memory and files@@ -175,24 +263,48 @@ GIT_EXTERN(void) git_config_free(git_config *cfg); /**+ * Get the git_config_entry of a config variable.+ *+ * The git_config_entry is owned by the config and should not be freed by the+ * user.++ * @param out pointer to the variable git_config_entry+ * @param cfg where to look for the variable+ * @param name the variable's name+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_config_get_entry(+ const git_config_entry **out,+ const git_config *cfg,+ const char *name);++/** * Get the value of an integer config variable. *+ * All config files will be looked into, in the order of their+ * defined level. A higher level means a higher priority. The+ * first occurence of the variable will be returned here.+ * * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name * @return 0 or an error code */-GIT_EXTERN(int) git_config_get_int32(int32_t *out, git_config *cfg, const char *name);+GIT_EXTERN(int) git_config_get_int32(int32_t *out, const git_config *cfg, const char *name); /** * Get the value of a long integer config variable. *+ * All config files will be looked into, in the order of their+ * defined level. A higher level means a higher priority. The+ * first occurrence of the variable will be returned here.+ * * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name * @return 0 or an error code */-GIT_EXTERN(int) git_config_get_int64(int64_t *out, git_config *cfg, const char *name);+GIT_EXTERN(int) git_config_get_int64(int64_t *out, const git_config *cfg, const char *name); /** * Get the value of a boolean config variable.@@ -200,12 +312,16 @@ * This function uses the usual C convention of 0 being false and * anything else true. *+ * All config files will be looked into, in the order of their+ * defined level. A higher level means a higher priority. The+ * first occurrence of the variable will be returned here.+ * * @param out pointer to the variable where the value should be stored * @param cfg where to look for the variable * @param name the variable's name * @return 0 or an error code */-GIT_EXTERN(int) git_config_get_bool(int *out, git_config *cfg, const char *name);+GIT_EXTERN(int) git_config_get_bool(int *out, const git_config *cfg, const char *name); /** * Get the value of a string config variable.@@ -213,12 +329,16 @@ * The string is owned by the variable and should not be freed by the * user. *+ * All config files will be looked into, in the order of their+ * defined level. A higher level means a higher priority. The+ * first occurrence of the variable will be returned here.+ * * @param out pointer to the variable's value * @param cfg where to look for the variable * @param name the variable's name * @return 0 or an error code */-GIT_EXTERN(int) git_config_get_string(const char **out, git_config *cfg, const char *name);+GIT_EXTERN(int) git_config_get_string(const char **out, const git_config *cfg, const char *name); /** * Get each value of a multivar.@@ -232,10 +352,11 @@ * @param fn the function to be called on each value of the variable * @param data opaque pointer to pass to the callback */-GIT_EXTERN(int) git_config_get_multivar(git_config *cfg, const char *name, const char *regexp, int (*fn)(const char *, void *), void *data);+GIT_EXTERN(int) git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp, git_config_foreach_cb callback, void *payload); /**- * Set the value of an integer config variable.+ * Set the value of an integer config variable in the config file+ * with the highest level (usually the local one). * * @param cfg where to look for the variable * @param name the variable's name@@ -245,7 +366,8 @@ GIT_EXTERN(int) git_config_set_int32(git_config *cfg, const char *name, int32_t value); /**- * Set the value of a long integer config variable.+ * Set the value of a long integer config variable in the config file+ * with the highest level (usually the local one). * * @param cfg where to look for the variable * @param name the variable's name@@ -255,7 +377,8 @@ GIT_EXTERN(int) git_config_set_int64(git_config *cfg, const char *name, int64_t value); /**- * Set the value of a boolean config variable.+ * Set the value of a boolean config variable in the config file+ * with the highest level (usually the local one). * * @param cfg where to look for the variable * @param name the variable's name@@ -265,7 +388,8 @@ GIT_EXTERN(int) git_config_set_bool(git_config *cfg, const char *name, int value); /**- * Set the value of a string config variable.+ * Set the value of a string config variable in the config file+ * with the highest level (usually the local one). * * A copy of the string is made and the user is free to use it * afterwards.@@ -277,9 +401,8 @@ */ GIT_EXTERN(int) git_config_set_string(git_config *cfg, const char *name, const char *value); - /**- * Set a multivar+ * Set a multivar in the local config file. * * @param cfg where to look for the variable * @param name the variable's name@@ -289,12 +412,13 @@ GIT_EXTERN(int) git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value); /**- * Delete a config variable+ * Delete a config variable from the config file+ * with the highest level (usually the local one). * * @param cfg the configuration * @param name the variable to delete */-GIT_EXTERN(int) git_config_delete(git_config *cfg, const char *name);+GIT_EXTERN(int) git_config_delete_entry(git_config *cfg, const char *name); /** * Perform an operation on each config variable.@@ -302,18 +426,36 @@ * The callback receives the normalized name and value of each variable * in the config backend, and the data pointer passed to this function. * As soon as one of the callback functions returns something other than 0,- * this function returns that value.+ * this function stops iterating and returns `GIT_EUSER`. * * @param cfg where to get the variables from * @param callback the function to call on each variable * @param payload the data to pass to the callback- * @return 0 or the return value of the callback which didn't return 0+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_config_foreach(- git_config *cfg,- int (*callback)(const char *var_name, const char *value, void *payload),+ const git_config *cfg,+ git_config_foreach_cb callback, void *payload); +/**+ * Perform an operation on each config variable matching a regular expression.+ *+ * This behaviors like `git_config_foreach` with an additional filter of a+ * regular expression that filters which config keys are passed to the+ * callback.+ *+ * @param cfg where to get the variables from+ * @param regexp regular expression to match against config names+ * @param callback the function to call on each variable+ * @param payload the data to pass to the callback+ * @return 0 or the return value of the callback which didn't return 0+ */+GIT_EXTERN(int) git_config_foreach_match(+ const git_config *cfg,+ const char *regexp,+ git_config_foreach_cb callback,+ void *payload); /** * Query the value of a config variable and return it mapped to@@ -324,7 +466,7 @@ * * A mapping array looks as follows: *- * git_cvar_map autocrlf_mapping[3] = {+ * git_cvar_map autocrlf_mapping[] = { * {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, * {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, * {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT},@@ -349,7 +491,63 @@ * @param map_n number of mapping objects in `maps` * @return 0 on success, error code otherwise */-GIT_EXTERN(int) git_config_get_mapped(int *out, git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n);+GIT_EXTERN(int) git_config_get_mapped(+ int *out,+ const git_config *cfg,+ const char *name,+ const git_cvar_map *maps,+ size_t map_n);++/**+ * Maps a string value to an integer constant+ *+ * @param out place to store the result of the parsing+ * @param maps array of `git_cvar_map` objects specifying the possible mappings+ * @param map_n number of mapping objects in `maps`+ * @param value value to parse+ */+GIT_EXTERN(int) git_config_lookup_map_value(+ int *out,+ const git_cvar_map *maps,+ size_t map_n,+ const char *value);++/**+ * Parse a string value as a bool.+ *+ * Valid values for true are: 'true', 'yes', 'on', 1 or any+ * number different from 0+ * Valid values for false are: 'false', 'no', 'off', 0+ *+ * @param out place to store the result of the parsing+ * @param value value to parse+ */+GIT_EXTERN(int) git_config_parse_bool(int *out, const char *value);++/**+ * Parse a string value as an int32.+ *+ * An optional value suffix of 'k', 'm', or 'g' will+ * cause the value to be multiplied by 1024, 1048576,+ * or 1073741824 prior to output.+ *+ * @param out place to store the result of the parsing+ * @param value value to parse+ */+GIT_EXTERN(int) git_config_parse_int32(int32_t *out, const char *value);++/**+ * Parse a string value as an int64.+ *+ * An optional value suffix of 'k', 'm', or 'g' will+ * cause the value to be multiplied by 1024, 1048576,+ * or 1073741824 prior to output.+ *+ * @param out place to store the result of the parsing+ * @param value value to parse+ */+GIT_EXTERN(int) git_config_parse_int64(int64_t *out, const char *value);+ /** @} */ GIT_END_DECL
+ libgit2/include/git2/cred_helpers.h view
@@ -0,0 +1,53 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_cred_helpers_h__+#define INCLUDE_git_cred_helpers_h__++#include "transport.h"++/**+ * @file git2/cred_helpers.h+ * @brief Utility functions for credential management+ * @defgroup git_cred_helpers credential management helpers+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Payload for git_cred_stock_userpass_plaintext.+ */+typedef struct git_cred_userpass_payload {+ char *username;+ char *password;+} git_cred_userpass_payload;+++/**+ * Stock callback usable as a git_cred_acquire_cb. This calls+ * git_cred_userpass_plaintext_new unless the protocol has not specified+ * GIT_CREDTYPE_USERPASS_PLAINTEXT as an allowed type.+ *+ * @param cred The newly created credential object.+ * @param url The resource for which we are demanding a credential.+ * @param username_from_url The username that was embedded in a "user@host"+ * remote url, or NULL if not included.+ * @param allowed_types A bitmask stating which cred types are OK to return.+ * @param payload The payload provided when specifying this callback. (This is+ * interpreted as a `git_cred_userpass_payload*`.)+ */+GIT_EXTERN(int) git_cred_userpass(+ git_cred **cred,+ const char *url,+ const char *user_from_url,+ unsigned int allowed_types,+ void *payload);+++/** @} */+GIT_END_DECL+#endif
libgit2/include/git2/diff.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -17,6 +17,9 @@ * @file git2/diff.h * @brief Git tree and file differencing routines. *+ * Overview+ * --------+ * * Calculating diffs is generally done in two phases: building a diff list * then traversing the diff list. This makes is easier to share logic * across the various types of diffs (tree vs tree, workdir vs index, etc.),@@ -24,156 +27,327 @@ * such as rename detected, in between the steps. When you are done with a * diff list object, it must be freed. *+ * Terminology+ * -----------+ *+ * To understand the diff APIs, you should know the following terms:+ *+ * - A `diff` or `diff list` represents the cumulative list of differences+ * between two snapshots of a repository (possibly filtered by a set of+ * file name patterns). This is the `git_diff_list` object.+ * - A `delta` is a file pair with an old and new revision. The old version+ * may be absent if the file was just created and the new version may be+ * absent if the file was deleted. A diff is mostly just a list of deltas.+ * - A `binary` file / delta is a file (or pair) for which no text diffs+ * should be generated. A diff list can contain delta entries that are+ * binary, but no diff content will be output for those files. There is+ * a base heuristic for binary detection and you can further tune the+ * behavior with git attributes or diff flags and option settings.+ * - A `hunk` is a span of modified lines in a delta along with some stable+ * surrounding context. You can configure the amount of context and other+ * properties of how hunks are generated. Each hunk also comes with a+ * header that described where it starts and ends in both the old and new+ * versions in the delta.+ * - A `line` is a range of characters inside a hunk. It could be a context+ * line (i.e. in both old and new versions), an added line (i.e. only in+ * the new version), or a removed line (i.e. only in the old version).+ * Unfortunately, we don't know anything about the encoding of data in the+ * file being diffed, so we cannot tell you much about the line content.+ * Line data will not be NUL-byte terminated, however, because it will be+ * just a span of bytes inside the larger file.+ * * @ingroup Git * @{ */ GIT_BEGIN_DECL -enum {+/**+ * Flags for diff options. A combination of these flags can be passed+ * in via the `flags` value in the `git_diff_options`.+ */+typedef enum {+ /** Normal diff, the default */ GIT_DIFF_NORMAL = 0,+ /** Reverse the sides of the diff */ GIT_DIFF_REVERSE = (1 << 0),+ /** Treat all files as text, disabling binary attributes & detection */ GIT_DIFF_FORCE_TEXT = (1 << 1),+ /** Ignore all whitespace */ GIT_DIFF_IGNORE_WHITESPACE = (1 << 2),+ /** Ignore changes in amount of whitespace */ GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3),+ /** Ignore whitespace at end of line */ GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4),+ /** Exclude submodules from the diff completely */ GIT_DIFF_IGNORE_SUBMODULES = (1 << 5),+ /** Use the "patience diff" algorithm (currently unimplemented) */ GIT_DIFF_PATIENCE = (1 << 6),+ /** Include ignored files in the diff list */ GIT_DIFF_INCLUDE_IGNORED = (1 << 7),+ /** Include untracked files in the diff list */ GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8),+ /** Include unmodified files in the diff list */ GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9),+ /** Even with GIT_DIFF_INCLUDE_UNTRACKED, an entire untracked directory+ * will be marked with only a single entry in the diff list; this flag+ * adds all files under the directory as UNTRACKED entries, too.+ */ GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10),-};+ /** If the pathspec is set in the diff options, this flags means to+ * apply it as an exact match instead of as an fnmatch pattern.+ */+ GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11),+ /** Use case insensitive filename comparisons */+ GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12),+ /** When generating patch text, include the content of untracked files */+ GIT_DIFF_INCLUDE_UNTRACKED_CONTENT = (1 << 13),+ /** Disable updating of the `binary` flag in delta records. This is+ * useful when iterating over a diff if you don't need hunk and data+ * callbacks and want to avoid having to load file completely.+ */+ GIT_DIFF_SKIP_BINARY_CHECK = (1 << 14),+ /** Normally, a type change between files will be converted into a+ * DELETED record for the old and an ADDED record for the new; this+ * options enabled the generation of TYPECHANGE delta records.+ */+ GIT_DIFF_INCLUDE_TYPECHANGE = (1 << 15),+ /** Even with GIT_DIFF_INCLUDE_TYPECHANGE, blob->tree changes still+ * generally show as a DELETED blob. This flag tries to correctly+ * label blob->tree transitions as TYPECHANGE records with new_file's+ * mode set to tree. Note: the tree SHA will not be available.+ */+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 16),+ /** Ignore file mode changes */+ GIT_DIFF_IGNORE_FILEMODE = (1 << 17),+ /** Even with GIT_DIFF_INCLUDE_IGNORED, an entire ignored directory+ * will be marked with only a single entry in the diff list; this flag+ * adds all files under the directory as IGNORED entries, too.+ */+ GIT_DIFF_RECURSE_IGNORED_DIRS = (1 << 18),+} git_diff_option_t; /**- * Structure describing options about how the diff should be executed.- *- * Setting all values of the structure to zero will yield the default- * values. Similarly, passing NULL for the options structure will- * give the defaults. The default values are marked below.+ * The diff list object that contains all individual file deltas. *- * @todo Most of the parameters here are not actually supported at this time.+ * This is an opaque structure which will be allocated by one of the diff+ * generator functions below (such as `git_diff_tree_to_tree`). You are+ * responsible for releasing the object memory when done, using the+ * `git_diff_list_free()` function. */-typedef struct {- uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */- uint16_t context_lines; /**< defaults to 3 */- uint16_t interhunk_lines; /**< defaults to 3 */- char *old_prefix; /**< defaults to "a" */- char *new_prefix; /**< defaults to "b" */- git_strarray pathspec; /**< defaults to show all paths */-} git_diff_options;+typedef struct git_diff_list git_diff_list; /**- * The diff list object that contains all individual file deltas.+ * Flags for the delta object and the file objects on each side.+ *+ * These flags are used for both the `flags` value of the `git_diff_delta`+ * and the flags for the `git_diff_file` objects representing the old and+ * new sides of the delta. Values outside of this public range should be+ * considered reserved for internal or future use. */-typedef struct git_diff_list git_diff_list;--enum {- GIT_DIFF_FILE_VALID_OID = (1 << 0),- GIT_DIFF_FILE_FREE_PATH = (1 << 1),- GIT_DIFF_FILE_BINARY = (1 << 2),- GIT_DIFF_FILE_NOT_BINARY = (1 << 3),- GIT_DIFF_FILE_FREE_DATA = (1 << 4),- GIT_DIFF_FILE_UNMAP_DATA = (1 << 5)-};+typedef enum {+ GIT_DIFF_FLAG_BINARY = (1 << 0), /** file(s) treated as binary data */+ GIT_DIFF_FLAG_NOT_BINARY = (1 << 1), /** file(s) treated as text data */+ GIT_DIFF_FLAG_VALID_OID = (1 << 2), /** `oid` value is known correct */+} git_diff_flag_t; /** * What type of change is described by a git_diff_delta?+ *+ * `GIT_DELTA_RENAMED` and `GIT_DELTA_COPIED` will only show up if you run+ * `git_diff_find_similar()` on the diff list object.+ *+ * `GIT_DELTA_TYPECHANGE` only shows up given `GIT_DIFF_INCLUDE_TYPECHANGE`+ * in the option flags (otherwise type changes will be split into ADDED /+ * DELETED pairs). */ typedef enum {- GIT_DELTA_UNMODIFIED = 0,- GIT_DELTA_ADDED = 1,- GIT_DELTA_DELETED = 2,- GIT_DELTA_MODIFIED = 3,- GIT_DELTA_RENAMED = 4,- GIT_DELTA_COPIED = 5,- GIT_DELTA_IGNORED = 6,- GIT_DELTA_UNTRACKED = 7+ GIT_DELTA_UNMODIFIED = 0, /** no changes */+ GIT_DELTA_ADDED = 1, /** entry does not exist in old version */+ GIT_DELTA_DELETED = 2, /** entry does not exist in new version */+ GIT_DELTA_MODIFIED = 3, /** entry content changed between old and new */+ GIT_DELTA_RENAMED = 4, /** entry was renamed between old and new */+ GIT_DELTA_COPIED = 5, /** entry was copied from another old entry */+ GIT_DELTA_IGNORED = 6, /** entry is ignored item in workdir */+ GIT_DELTA_UNTRACKED = 7, /** entry is untracked item in workdir */+ GIT_DELTA_TYPECHANGE = 8, /** type of entry changed between old and new */ } git_delta_t; /**- * Description of one side of a diff.+ * Description of one side of a diff entry.+ *+ * Although this is called a "file", it may actually represent a file, a+ * symbolic link, a submodule commit id, or even a tree (although that only+ * if you are tracking type changes or ignored/untracked directories).+ *+ * The `oid` is the `git_oid` of the item. If the entry represents an+ * absent side of a diff (e.g. the `old_file` of a `GIT_DELTA_ADDED` delta),+ * then the oid will be zeroes.+ *+ * `path` is the NUL-terminated path to the entry relative to the working+ * directory of the repository.+ *+ * `size` is the size of the entry in bytes.+ *+ * `flags` is a combination of the `git_diff_flag_t` types+ *+ * `mode` is, roughly, the stat() `st_mode` value for the item. This will+ * be restricted to one of the `git_filemode_t` values. */ typedef struct {- git_oid oid;- char *path;- uint16_t mode;- git_off_t size;- unsigned int flags;+ git_oid oid;+ const char *path;+ git_off_t size;+ uint32_t flags;+ uint16_t mode; } git_diff_file; /**- * Description of changes to one file.+ * Description of changes to one entry. *- * When iterating over a diff list object, this will generally be passed to- * most callback functions and you can use the contents to understand- * exactly what has changed.+ * When iterating over a diff list object, this will be passed to most+ * callback functions and you can use the contents to understand exactly+ * what has changed. *- * Under some circumstances, not all fields will be filled in, but the code- * generally tries to fill in as much as possible. One example is that the- * "binary" field will not actually look at file contents if you do not- * pass in hunk and/or line callbacks to the diff foreach iteration function.- * It will just use the git attributes for those files.+ * The `old_file` represents the "from" side of the diff and the `new_file`+ * represents to "to" side of the diff. What those means depend on the+ * function that was used to generate the diff and will be documented below.+ * You can also use the `GIT_DIFF_REVERSE` flag to flip it around.+ *+ * Although the two sides of the delta are named "old_file" and "new_file",+ * they actually may correspond to entries that represent a file, a symbolic+ * link, a submodule commit id, or even a tree (if you are tracking type+ * changes or ignored/untracked directories).+ *+ * Under some circumstances, in the name of efficiency, not all fields will+ * be filled in, but we generally try to fill in as much as possible. One+ * example is that the "flags" field may not have either the `BINARY` or the+ * `NOT_BINARY` flag set to avoid examining file contents if you do not pass+ * in hunk and/or line callbacks to the diff foreach iteration function. It+ * will just use the git attributes for those files. */ typedef struct { git_diff_file old_file; git_diff_file new_file; git_delta_t status;- unsigned int similarity; /**< for RENAMED and COPIED, value 0-100 */- int binary;+ uint32_t similarity; /**< for RENAMED and COPIED, value 0-100 */+ uint32_t flags; } git_diff_delta; /**+ * Diff notification callback function.+ *+ * The callback will be called for each file, just before the `git_delta_t`+ * gets inserted into the diff list.+ *+ * When the callback:+ * - returns < 0, the diff process will be aborted.+ * - returns > 0, the delta will not be inserted into the diff list, but the+ * diff process continues.+ * - returns 0, the delta is inserted into the diff list, and the diff process+ * continues.+ */+typedef int (*git_diff_notify_cb)(+ const git_diff_list *diff_so_far,+ const git_diff_delta *delta_to_add,+ const char *matched_pathspec,+ void *payload);++/**+ * Structure describing options about how the diff should be executed.+ *+ * Setting all values of the structure to zero will yield the default+ * values. Similarly, passing NULL for the options structure will+ * give the defaults. The default values are marked below.+ *+ * - `flags` is a combination of the `git_diff_option_t` values above+ * - `context_lines` is the number of unchanged lines that define the+ * boundary of a hunk (and to display before and after)+ * - `interhunk_lines` is the maximum number of unchanged lines between+ * hunk boundaries before the hunks will be merged into a one.+ * - `old_prefix` is the virtual "directory" to prefix to old file names+ * in hunk headers (default "a")+ * - `new_prefix` is the virtual "directory" to prefix to new file names+ * in hunk headers (default "b")+ * - `pathspec` is an array of paths / fnmatch patterns to constrain diff+ * - `max_size` is a file size (in bytes) above which a blob will be marked+ * as binary automatically; pass a negative value to disable.+ * - `notify_cb` is an optional callback function, notifying the consumer of+ * which files are being examined as the diff is generated+ * - `notify_payload` is the payload data to pass to the `notify_cb` function+ */+typedef struct {+ unsigned int version; /**< version for the struct */+ uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */+ uint16_t context_lines; /**< defaults to 3 */+ uint16_t interhunk_lines; /**< defaults to 0 */+ const char *old_prefix; /**< defaults to "a" */+ const char *new_prefix; /**< defaults to "b" */+ git_strarray pathspec; /**< defaults to include all paths */+ git_off_t max_size; /**< defaults to 512MB */+ git_diff_notify_cb notify_cb;+ void *notify_payload;+} git_diff_options;++#define GIT_DIFF_OPTIONS_VERSION 1+#define GIT_DIFF_OPTIONS_INIT {GIT_DIFF_OPTIONS_VERSION, GIT_DIFF_NORMAL, 3}++/** * When iterating over a diff, callback that will be made per file.+ *+ * @param delta A pointer to the delta data for the file+ * @param progress Goes from 0 to 1 over the diff list+ * @param payload User-specified pointer from foreach function */-typedef int (*git_diff_file_fn)(- void *cb_data,- git_diff_delta *delta,- float progress);+typedef int (*git_diff_file_cb)(+ const git_diff_delta *delta,+ float progress,+ void *payload); /** * Structure describing a hunk of a diff. */ typedef struct {- int old_start;- int old_lines;- int new_start;- int new_lines;+ int old_start; /** Starting line number in old_file */+ int old_lines; /** Number of lines in old_file */+ int new_start; /** Starting line number in new_file */+ int new_lines; /** Number of lines in new_file */ } git_diff_range; /** * When iterating over a diff, callback that will be made per hunk. */-typedef int (*git_diff_hunk_fn)(- void *cb_data,- git_diff_delta *delta,- git_diff_range *range,+typedef int (*git_diff_hunk_cb)(+ const git_diff_delta *delta,+ const git_diff_range *range, const char *header,- size_t header_len);+ size_t header_len,+ void *payload); /** * Line origin constants. * * These values describe where a line came from and will be passed to- * the git_diff_data_fn when iterating over a diff. There are some- * special origin contants at the end that are used for the text+ * the git_diff_data_cb when iterating over a diff. There are some+ * special origin constants at the end that are used for the text * output callbacks to demarcate lines that are actually part of * the file or hunk headers. */-enum {- /* these values will be sent to `git_diff_data_fn` along with the line */+typedef enum {+ /* These values will be sent to `git_diff_data_cb` along with the line */ GIT_DIFF_LINE_CONTEXT = ' ', GIT_DIFF_LINE_ADDITION = '+', GIT_DIFF_LINE_DELETION = '-',- GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< LF was added at end of file */+ GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< Removed line w/o LF & added one with */ GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */- /* these values will only be sent to a `git_diff_data_fn` when the content- * of a diff is being formatted (eg. through git_diff_print_patch() or- * git_diff_print_compact(), for instance).++ /* The following values will only be sent to a `git_diff_data_cb` when+ * the content of a diff is being formatted (eg. through+ * git_diff_print_patch() or git_diff_print_compact(), for instance). */ GIT_DIFF_LINE_FILE_HDR = 'F', GIT_DIFF_LINE_HUNK_HDR = 'H', GIT_DIFF_LINE_BINARY = 'B'-};+} git_diff_line_t; /** * When iterating over a diff, callback that will be made per text diff@@ -183,14 +357,109 @@ * of text. This uses some extra GIT_DIFF_LINE_... constants for output * of lines of file and hunk headers. */-typedef int (*git_diff_data_fn)(- void *cb_data,- git_diff_delta *delta,- git_diff_range *range,- char line_origin, /**< GIT_DIFF_LINE_... value from above */- const char *content,- size_t content_len);+typedef int (*git_diff_data_cb)(+ const git_diff_delta *delta, /** delta that contains this data */+ const git_diff_range *range, /** range of lines containing this data */+ char line_origin, /** git_diff_list_t value from above */+ const char *content, /** diff data - not NUL terminated */+ size_t content_len, /** number of bytes of diff data */+ void *payload); /** user reference data */ +/**+ * The diff patch is used to store all the text diffs for a delta.+ *+ * You can easily loop over the content of patches and get information about+ * them.+ */+typedef struct git_diff_patch git_diff_patch;++/**+ * Flags to control the behavior of diff rename/copy detection.+ */+typedef enum {+ /** look for renames? (`--find-renames`) */+ GIT_DIFF_FIND_RENAMES = (1 << 0),+ /** consider old side of modified for renames? (`--break-rewrites=N`) */+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES = (1 << 1),++ /** look for copies? (a la `--find-copies`) */+ GIT_DIFF_FIND_COPIES = (1 << 2),+ /** consider unmodified as copy sources? (`--find-copies-harder`) */+ GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1 << 3),++ /** split large rewrites into delete/add pairs (`--break-rewrites=/M`) */+ GIT_DIFF_FIND_AND_BREAK_REWRITES = (1 << 4),++ /** turn on all finding features */+ GIT_DIFF_FIND_ALL = (0x1f),++ /** measure similarity ignoring leading whitespace (default) */+ GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0,+ /** measure similarity ignoring all whitespace */+ GIT_DIFF_FIND_IGNORE_WHITESPACE = (1 << 6),+ /** measure similarity including all data */+ GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 7),+} git_diff_find_t;++/**+ * Pluggable similarity metric+ */+typedef struct {+ int (*file_signature)(+ void **out, const git_diff_file *file,+ const char *fullpath, void *payload);+ int (*buffer_signature)(+ void **out, const git_diff_file *file,+ const char *buf, size_t buflen, void *payload);+ void (*free_signature)(void *sig, void *payload);+ int (*similarity)(int *score, void *siga, void *sigb, void *payload);+ void *payload;+} git_diff_similarity_metric;++/**+ * Control behavior of rename and copy detection+ *+ * These options mostly mimic parameters that can be passed to git-diff.+ *+ * - `rename_threshold` is the same as the -M option with a value+ * - `copy_threshold` is the same as the -C option with a value+ * - `rename_from_rewrite_threshold` matches the top of the -B option+ * - `break_rewrite_threshold` matches the bottom of the -B option+ * - `target_limit` matches the -l option+ *+ * The `metric` option allows you to plug in a custom similarity metric.+ * Set it to NULL for the default internal metric which is based on sampling+ * hashes of ranges of data in the file. The default metric is a pretty+ * good similarity approximation that should work fairly well for both text+ * and binary data, and is pretty fast with fixed memory overhead.+ */+typedef struct {+ unsigned int version;++ /** Combination of git_diff_find_t values (default FIND_RENAMES) */+ unsigned int flags;++ /** Similarity to consider a file renamed (default 50) */+ unsigned int rename_threshold;+ /** Similarity of modified to be eligible rename source (default 50) */+ unsigned int rename_from_rewrite_threshold;+ /** Similarity to consider a file a copy (default 50) */+ unsigned int copy_threshold;+ /** Similarity to split modify into delete/add pair (default 60) */+ unsigned int break_rewrite_threshold;++ /** Maximum similarity sources to examine (a la diff's `-l` option or+ * the `diff.renameLimit` config) (default 200)+ */+ unsigned int target_limit;++ /** Pluggable similarity metric; pass NULL to use internal metric */+ git_diff_similarity_metric *metric;+} git_diff_find_options;++#define GIT_DIFF_FIND_OPTIONS_VERSION 1+#define GIT_DIFF_FIND_OPTIONS_INIT {GIT_DIFF_FIND_OPTIONS_VERSION}+ /** @name Diff List Generator Functions * * These are the functions you would use to create (or destroy) a@@ -204,69 +473,104 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff); /**- * Compute a difference between two tree objects.+ * Create a diff list with the difference between two tree objects. *+ * This is equivalent to `git diff <old-tree> <new-tree>`+ *+ * The first tree will be used for the "old_file" side of the delta and the+ * second tree will be used for the "new_file" side of the delta.+ *+ * @param diff Output pointer to a git_diff_list pointer to be allocated. * @param repo The repository containing the trees.- * @param opts Structure with options to influence diff or NULL for defaults. * @param old_tree A git_tree object to diff from. * @param new_tree A git_tree object to diff to.- * @param diff A pointer to a git_diff_list pointer that will be allocated.+ * @param opts Structure with options to influence diff or NULL for defaults. */ GIT_EXTERN(int) git_diff_tree_to_tree(+ git_diff_list **diff, git_repository *repo,- const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old_tree, git_tree *new_tree,- git_diff_list **diff);+ const git_diff_options *opts); /**< can be NULL for defaults */ /**- * Compute a difference between a tree and the index.+ * Create a diff list between a tree and repository index. *+ * This is equivalent to `git diff --cached <treeish>` or if you pass+ * the HEAD tree, then like `git diff --cached`.+ *+ * The tree you pass will be used for the "old_file" side of the delta, and+ * the index will be used for the "new_file" side of the delta.+ *+ * @param diff Output pointer to a git_diff_list pointer to be allocated. * @param repo The repository containing the tree and index.- * @param opts Structure with options to influence diff or NULL for defaults. * @param old_tree A git_tree object to diff from.- * @param diff A pointer to a git_diff_list pointer that will be allocated.+ * @param index The index to diff with; repo index used if NULL.+ * @param opts Structure with options to influence diff or NULL for defaults. */-GIT_EXTERN(int) git_diff_index_to_tree(+GIT_EXTERN(int) git_diff_tree_to_index(+ git_diff_list **diff, git_repository *repo,- const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old_tree,- git_diff_list **diff);+ git_index *index,+ const git_diff_options *opts); /**< can be NULL for defaults */ /**- * Compute a difference between the working directory and the index.+ * Create a diff list between the repository index and the workdir directory. *+ * This matches the `git diff` command. See the note below on+ * `git_diff_tree_to_workdir` for a discussion of the difference between+ * `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`+ * using libgit2.+ *+ * The index will be used for the "old_file" side of the delta, and the+ * working directory will be used for the "new_file" side of the delta.+ *+ * @param diff Output pointer to a git_diff_list pointer to be allocated. * @param repo The repository.+ * @param index The index to diff from; repo index used if NULL. * @param opts Structure with options to influence diff or NULL for defaults.- * @param diff A pointer to a git_diff_list pointer that will be allocated. */-GIT_EXTERN(int) git_diff_workdir_to_index(+GIT_EXTERN(int) git_diff_index_to_workdir(+ git_diff_list **diff, git_repository *repo,- const git_diff_options *opts, /**< can be NULL for defaults */- git_diff_list **diff);+ git_index *index,+ const git_diff_options *opts); /**< can be NULL for defaults */ /**- * Compute a difference between the working directory and a tree.+ * Create a diff list between a tree and the working directory. *- * This returns strictly the differences between the tree and the- * files contained in the working directory, regardless of the state- * of files in the index. There is no direct equivalent in C git.+ * The tree you provide will be used for the "old_file" side of the delta,+ * and the working directory will be used for the "new_file" side. *- * This is *NOT* the same as 'git diff HEAD' or 'git diff <SHA>'. Those- * commands diff the tree, the index, and the workdir. To emulate those- * functions, call `git_diff_index_to_tree` and `git_diff_workdir_to_index`,- * then call `git_diff_merge` on the results.+ * Please note: this is *NOT* the same as `git diff <treeish>`. Running+ * `git diff HEAD` or the like actually uses information from the index,+ * along with the tree and working directory info. *+ * This function returns strictly the differences between the tree and the+ * files contained in the working directory, regardless of the state of+ * files in the index. It may come as a surprise, but there is no direct+ * equivalent in core git.+ *+ * To emulate `git diff <treeish>`, call both `git_diff_tree_to_index` and+ * `git_diff_index_to_workdir`, then call `git_diff_merge` on the results.+ * That will yield a `git_diff_list` that matches the git output.+ *+ * If this seems confusing, take the case of a file with a staged deletion+ * where the file has then been put back into the working dir and modified.+ * The tree-to-workdir diff for that file is 'modified', but core git would+ * show status 'deleted' since there is a pending deletion in the index.+ *+ * @param diff A pointer to a git_diff_list pointer that will be allocated. * @param repo The repository containing the tree.- * @param opts Structure with options to influence diff or NULL for defaults. * @param old_tree A git_tree object to diff from.- * @param diff A pointer to a git_diff_list pointer that will be allocated.+ * @param opts Structure with options to influence diff or NULL for defaults. */-GIT_EXTERN(int) git_diff_workdir_to_tree(+GIT_EXTERN(int) git_diff_tree_to_workdir(+ git_diff_list **diff, git_repository *repo,- const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old_tree,- git_diff_list **diff);+ const git_diff_options *opts); /**< can be NULL for defaults */ /** * Merge one diff list into another.@@ -285,6 +589,22 @@ git_diff_list *onto, const git_diff_list *from); +/**+ * Transform a diff list marking file renames, copies, etc.+ *+ * This modifies a diff list in place, replacing old entries that look+ * like renames or copies with new entries reflecting those changes.+ * This also will, if requested, break modified files into add/remove+ * pairs if the amount of change is above a threshold.+ *+ * @param diff Diff list to run detection algorithms on+ * @param options Control how detection should be run, NULL for defaults+ * @return 0 on success, -1 on failure+ */+GIT_EXTERN(int) git_diff_find_similar(+ git_diff_list *diff,+ git_diff_find_options *options);+ /**@}*/ @@ -296,38 +616,274 @@ /**@{*/ /**- * Iterate over a diff list issuing callbacks.+ * Loop over all deltas in a diff list issuing callbacks. *- * If the hunk and/or line callbacks are not NULL, then this will calculate- * text diffs for all files it thinks are not binary. If those are both- * NULL, then this will not bother with the text diffs, so it can be- * efficient.+ * This will iterate through all of the files described in a diff. You+ * should provide a file callback to learn about each file.+ *+ * The "hunk" and "line" callbacks are optional, and the text diff of the+ * files will only be calculated if they are not NULL. Of course, these+ * callbacks will not be invoked for binary files on the diff list or for+ * files whose only changed is a file mode change.+ *+ * Returning a non-zero value from any of the callbacks will terminate+ * the iteration and cause this return `GIT_EUSER`.+ *+ * @param diff A git_diff_list generated by one of the above functions.+ * @param file_cb Callback function to make per file in the diff.+ * @param hunk_cb Optional callback to make per hunk of text diff. This+ * callback is called to describe a range of lines in the+ * diff. It will not be issued for binary files.+ * @param line_cb Optional callback to make per line of diff text. This+ * same callback will be made for context lines, added, and+ * removed lines, and even for a deleted trailing newline.+ * @param payload Reference pointer that will be passed to your callbacks.+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_foreach( git_diff_list *diff,- void *cb_data,- git_diff_file_fn file_cb,- git_diff_hunk_fn hunk_cb,- git_diff_data_fn line_cb);+ git_diff_file_cb file_cb,+ git_diff_hunk_cb hunk_cb,+ git_diff_data_cb line_cb,+ void *payload); /** * Iterate over a diff generating text output like "git diff --name-status".+ *+ * Returning a non-zero value from the callbacks will terminate the+ * iteration and cause this return `GIT_EUSER`.+ *+ * @param diff A git_diff_list generated by one of the above functions.+ * @param print_cb Callback to make per line of diff text.+ * @param payload Reference pointer that will be passed to your callback.+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_print_compact( git_diff_list *diff,- void *cb_data,- git_diff_data_fn print_cb);+ git_diff_data_cb print_cb,+ void *payload); /**+ * Look up the single character abbreviation for a delta status code.+ *+ * When you call `git_diff_print_compact` it prints single letter codes into+ * the output such as 'A' for added, 'D' for deleted, 'M' for modified, etc.+ * It is sometimes convenient to convert a git_delta_t value into these+ * letters for your own purposes. This function does just that. By the+ * way, unmodified will return a space (i.e. ' ').+ *+ * @param delta_t The git_delta_t value to look up+ * @return The single character label for that code+ */+GIT_EXTERN(char) git_diff_status_char(git_delta_t status);++/** * Iterate over a diff generating text output like "git diff". * * This is a super easy way to generate a patch from a diff.+ *+ * Returning a non-zero value from the callbacks will terminate the+ * iteration and cause this return `GIT_EUSER`.+ *+ * @param diff A git_diff_list generated by one of the above functions.+ * @param payload Reference pointer that will be passed to your callbacks.+ * @param print_cb Callback function to output lines of the diff. This+ * same function will be called for file headers, hunk+ * headers, and diff lines. Fortunately, you can probably+ * use various GIT_DIFF_LINE constants to determine what+ * text you are given.+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_print_patch( git_diff_list *diff,- void *cb_data,- git_diff_data_fn print_cb);+ git_diff_data_cb print_cb,+ void *payload); +/**+ * Query how many diff records are there in a diff list.+ *+ * @param diff A git_diff_list generated by one of the above functions+ * @return Count of number of deltas in the list+ */+GIT_EXTERN(size_t) git_diff_num_deltas(git_diff_list *diff);++/**+ * Query how many diff deltas are there in a diff list filtered by type.+ *+ * This works just like `git_diff_entrycount()` with an extra parameter+ * that is a `git_delta_t` and returns just the count of how many deltas+ * match that particular type.+ *+ * @param diff A git_diff_list generated by one of the above functions+ * @param type A git_delta_t value to filter the count+ * @return Count of number of deltas matching delta_t type+ */+GIT_EXTERN(size_t) git_diff_num_deltas_of_type(+ git_diff_list *diff,+ git_delta_t type);++/**+ * Return the diff delta and patch for an entry in the diff list.+ *+ * The `git_diff_patch` is a newly created object contains the text diffs+ * for the delta. You have to call `git_diff_patch_free()` when you are+ * done with it. You can use the patch object to loop over all the hunks+ * and lines in the diff of the one delta.+ *+ * For an unchanged file or a binary file, no `git_diff_patch` will be+ * created, the output will be set to NULL, and the `binary` flag will be+ * set true in the `git_diff_delta` structure.+ *+ * The `git_diff_delta` pointer points to internal data and you do not have+ * to release it when you are done with it. It will go away when the+ * `git_diff_list` and `git_diff_patch` go away.+ *+ * It is okay to pass NULL for either of the output parameters; if you pass+ * NULL for the `git_diff_patch`, then the text diff will not be calculated.+ *+ * @param patch_out Output parameter for the delta patch object+ * @param delta_out Output parameter for the delta object+ * @param diff Diff list object+ * @param idx Index into diff list+ * @return 0 on success, other value < 0 on error+ */+GIT_EXTERN(int) git_diff_get_patch(+ git_diff_patch **patch_out,+ const git_diff_delta **delta_out,+ git_diff_list *diff,+ size_t idx);++/**+ * Free a git_diff_patch object.+ */+GIT_EXTERN(void) git_diff_patch_free(+ git_diff_patch *patch);++/**+ * Get the delta associated with a patch+ */+GIT_EXTERN(const git_diff_delta *) git_diff_patch_delta(+ git_diff_patch *patch);++/**+ * Get the number of hunks in a patch+ */+GIT_EXTERN(size_t) git_diff_patch_num_hunks(+ git_diff_patch *patch);++/**+ * Get line counts of each type in a patch.+ *+ * This helps imitate a diff --numstat type of output. For that purpose,+ * you only need the `total_additions` and `total_deletions` values, but we+ * include the `total_context` line count in case you want the total number+ * of lines of diff output that will be generated.+ *+ * All outputs are optional. Pass NULL if you don't need a particular count.+ *+ * @param total_context Count of context lines in output, can be NULL.+ * @param total_additions Count of addition lines in output, can be NULL.+ * @param total_deletions Count of deletion lines in output, can be NULL.+ * @param patch The git_diff_patch object+ * @return Number of lines in hunk or -1 if invalid hunk index+ */+GIT_EXTERN(int) git_diff_patch_line_stats(+ size_t *total_context,+ size_t *total_additions,+ size_t *total_deletions,+ const git_diff_patch *patch);++/**+ * Get the information about a hunk in a patch+ *+ * Given a patch and a hunk index into the patch, this returns detailed+ * information about that hunk. Any of the output pointers can be passed+ * as NULL if you don't care about that particular piece of information.+ *+ * @param range Output pointer to git_diff_range of hunk+ * @param header Output pointer to header string for hunk. Unlike the+ * content pointer for each line, this will be NUL-terminated+ * @param header_len Output value of characters in header string+ * @param lines_in_hunk Output count of total lines in this hunk+ * @param patch Input pointer to patch object+ * @param hunk_idx Input index of hunk to get information about+ * @return 0 on success, GIT_ENOTFOUND if hunk_idx out of range, <0 on error+ */+GIT_EXTERN(int) git_diff_patch_get_hunk(+ const git_diff_range **range,+ const char **header,+ size_t *header_len,+ size_t *lines_in_hunk,+ git_diff_patch *patch,+ size_t hunk_idx);++/**+ * Get the number of lines in a hunk.+ *+ * @param patch The git_diff_patch object+ * @param hunk_idx Index of the hunk+ * @return Number of lines in hunk or -1 if invalid hunk index+ */+GIT_EXTERN(int) git_diff_patch_num_lines_in_hunk(+ git_diff_patch *patch,+ size_t hunk_idx);++/**+ * Get data about a line in a hunk of a patch.+ *+ * Given a patch, a hunk index, and a line index in the hunk, this+ * will return a lot of details about that line. If you pass a hunk+ * index larger than the number of hunks or a line index larger than+ * the number of lines in the hunk, this will return -1.+ *+ * @param line_origin A GIT_DIFF_LINE constant from above+ * @param content Pointer to content of diff line, not NUL-terminated+ * @param content_len Number of characters in content+ * @param old_lineno Line number in old file or -1 if line is added+ * @param new_lineno Line number in new file or -1 if line is deleted+ * @param patch The patch to look in+ * @param hunk_idx The index of the hunk+ * @param line_of_index The index of the line in the hunk+ * @return 0 on success, <0 on failure+ */+GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(+ char *line_origin,+ const char **content,+ size_t *content_len,+ int *old_lineno,+ int *new_lineno,+ git_diff_patch *patch,+ size_t hunk_idx,+ size_t line_of_hunk);++/**+ * Serialize the patch to text via callback.+ *+ * Returning a non-zero value from the callback will terminate the iteration+ * and cause this return `GIT_EUSER`.+ *+ * @param patch A git_diff_patch representing changes to one file+ * @param print_cb Callback function to output lines of the patch. Will be+ * called for file headers, hunk headers, and diff lines.+ * @param payload Reference pointer that will be passed to your callbacks.+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code+ */+GIT_EXTERN(int) git_diff_patch_print(+ git_diff_patch *patch,+ git_diff_data_cb print_cb,+ void *payload);++/**+ * Get the content of a patch as a single diff text.+ *+ * @param string Allocated string; caller must free.+ * @param patch A git_diff_patch representing changes to one file+ * @return 0 on success, <0 on failure.+ */+GIT_EXTERN(int) git_diff_patch_to_str(+ char **string,+ git_diff_patch *patch);+ /**@}*/ @@ -336,24 +892,55 @@ */ /**- * Directly run a text diff on two blobs.+ * Directly run a diff on two blobs. *- * Compared to a file, a blob lacks some contextual information. As such, the- * `git_diff_file` parameters of the callbacks will be filled accordingly to the following:- * `mode` will be set to 0, `path` will be set to NULL. When dealing with a NULL blob, `oid`- * will be set to 0.+ * Compared to a file, a blob lacks some contextual information. As such,+ * the `git_diff_file` given to the callback will have some fake data; i.e.+ * `mode` will be 0 and `path` will be NULL. *- * When at least one of the blobs being dealt with is binary, the `git_diff_delta` binary- * attribute will be set to 1 and no call to the hunk_cb nor line_cb will be made.+ * NULL is allowed for either `old_blob` or `new_blob` and will be treated+ * as an empty blob, with the `oid` set to NULL in the `git_diff_file` data.+ * Passing NULL for both blobs is a noop; no callbacks will be made at all.+ *+ * We do run a binary content check on the blob content and if either blob+ * looks like binary data, the `git_diff_delta` binary attribute will be set+ * to 1 and no call to the hunk_cb nor line_cb will be made (unless you pass+ * `GIT_DIFF_FORCE_TEXT` of course).+ *+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_blobs(- git_blob *old_blob,- git_blob *new_blob,- git_diff_options *options,- void *cb_data,- git_diff_file_fn file_cb,- git_diff_hunk_fn hunk_cb,- git_diff_data_fn line_cb);+ const git_blob *old_blob,+ const git_blob *new_blob,+ const git_diff_options *options,+ git_diff_file_cb file_cb,+ git_diff_hunk_cb hunk_cb,+ git_diff_data_cb line_cb,+ void *payload);++/**+ * Directly run a diff between a blob and a buffer.+ *+ * As with `git_diff_blobs`, comparing a blob and buffer lacks some context,+ * so the `git_diff_file` parameters to the callbacks will be faked a la the+ * rules for `git_diff_blobs()`.+ *+ * Passing NULL for `old_blob` will be treated as an empty blob (i.e. the+ * `file_cb` will be invoked with GIT_DELTA_ADDED and the diff will be the+ * entire content of the buffer added). Passing NULL to the buffer will do+ * the reverse, with GIT_DELTA_REMOVED and blob content removed.+ *+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code+ */+GIT_EXTERN(int) git_diff_blob_to_buffer(+ const git_blob *old_blob,+ const char *buffer,+ size_t buffer_len,+ const git_diff_options *options,+ git_diff_file_cb file_cb,+ git_diff_hunk_cb hunk_cb,+ git_diff_data_cb data_cb,+ void *payload); GIT_END_DECL
libgit2/include/git2/errors.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -17,43 +17,6 @@ */ GIT_BEGIN_DECL -#ifdef GIT_OLD_ERRORS-enum {- GIT_SUCCESS = 0,- GIT_ENOTOID = -2,- GIT_ENOTFOUND = -3,- GIT_ENOMEM = -4,- GIT_EOSERR = -5,- GIT_EOBJTYPE = -6,- GIT_ENOTAREPO = -7,- GIT_EINVALIDTYPE = -8,- GIT_EMISSINGOBJDATA = -9,- GIT_EPACKCORRUPTED = -10,- GIT_EFLOCKFAIL = -11,- GIT_EZLIB = -12,- GIT_EBUSY = -13,- GIT_EBAREINDEX = -14,- GIT_EINVALIDREFNAME = -15,- GIT_EREFCORRUPTED = -16,- GIT_ETOONESTEDSYMREF = -17,- GIT_EPACKEDREFSCORRUPTED = -18,- GIT_EINVALIDPATH = -19,- GIT_EREVWALKOVER = -20,- GIT_EINVALIDREFSTATE = -21,- GIT_ENOTIMPLEMENTED = -22,- GIT_EEXISTS = -23,- GIT_EOVERFLOW = -24,- GIT_ENOTNUM = -25,- GIT_ESTREAM = -26,- GIT_EINVALIDARGS = -27,- GIT_EOBJCORRUPTED = -28,- GIT_EAMBIGUOUS = -29,- GIT_EPASSTHROUGH = -30,- GIT_ENOMATCH = -31,- GIT_ESHORTBUFFER = -32,-};-#endif- /** Generic return codes */ enum { GIT_OK = 0,@@ -62,9 +25,16 @@ GIT_EEXISTS = -4, GIT_EAMBIGUOUS = -5, GIT_EBUFS = -6,+ GIT_EUSER = -7,+ GIT_EBAREREPO = -8,+ GIT_EORPHANEDHEAD = -9,+ GIT_EUNMERGED = -10,+ GIT_ENONFASTFORWARD = -11,+ GIT_EINVALIDSPEC = -12,+ GIT_EMERGECONFLICT = -13, GIT_PASSTHROUGH = -30,- GIT_REVWALKOVER = -31,+ GIT_ITEROVER = -31, }; typedef struct {@@ -72,6 +42,7 @@ int klass; } git_error; +/** Error classes */ typedef enum { GITERR_NOMEMORY, GITERR_OS,@@ -88,6 +59,13 @@ GITERR_TAG, GITERR_TREE, GITERR_INDEXER,+ GITERR_SSL,+ GITERR_SUBMODULE,+ GITERR_THREAD,+ GITERR_STASH,+ GITERR_CHECKOUT,+ GITERR_FETCHHEAD,+ GITERR_MERGE, } git_error_t; /**@@ -102,6 +80,40 @@ * Clear the last library error that occurred for this thread. */ GIT_EXTERN(void) giterr_clear(void);++/**+ * Set the error message string for this thread.+ *+ * This function is public so that custom ODB backends and the like can+ * relay an error message through libgit2. Most regular users of libgit2+ * will never need to call this function -- actually, calling it in most+ * circumstances (for example, calling from within a callback function)+ * will just end up having the value overwritten by libgit2 internals.+ *+ * This error message is stored in thread-local storage and only applies+ * to the particular thread that this libgit2 call is made from.+ *+ * NOTE: Passing the `error_class` as GITERR_OS has a special behavior: we+ * attempt to append the system default error message for the last OS error+ * that occurred and then clear the last error. The specific implementation+ * of looking up and clearing this last OS error will vary by platform.+ *+ * @param error_class One of the `git_error_t` enum above describing the+ * general subsystem that is responsible for the error.+ * @param message The formatted error message to keep+ */+GIT_EXTERN(void) giterr_set_str(int error_class, const char *string);++/**+ * Set the error message to a special value for memory allocation failure.+ *+ * The normal `giterr_set_str()` function attempts to `strdup()` the string+ * that is passed in. This is not a good idea when the error in question+ * is a memory allocation failure. That circumstance has a special setter+ * function that sets the error string to a known and statically allocated+ * internal value.+ */+GIT_EXTERN(void) giterr_set_oom(void); /** @} */ GIT_END_DECL
+ libgit2/include/git2/graph.h view
@@ -0,0 +1,41 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_graph_h__+#define INCLUDE_git_graph_h__++#include "common.h"+#include "types.h"+#include "oid.h"++/**+ * @file git2/graph.h+ * @brief Git graph traversal routines+ * @defgroup git_revwalk Git graph traversal routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Count the number of unique commits between two commit objects+ *+ * There is no need for branches containing the commits to have any+ * upstream relationship, but it helps to think of one as a branch and+ * the other as its upstream, the `ahead` and `behind` values will be+ * what git would report for the branches.+ *+ * @param ahead number of unique from commits in `upstream`+ * @param behind number of unique from commits in `local`+ * @param repo the repository where the commits exist+ * @param local the commit for local+ * @param upstream the commit for upstream+ */+GIT_EXTERN(int) git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo, const git_oid *local, const git_oid *upstream);++/** @} */+GIT_END_DECL+#endif
+ libgit2/include/git2/ignore.h view
@@ -0,0 +1,78 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_ignore_h__+#define INCLUDE_git_ignore_h__++#include "common.h"+#include "types.h"++GIT_BEGIN_DECL++/**+ * Add ignore rules for a repository.+ *+ * Excludesfile rules (i.e. .gitignore rules) are generally read from+ * .gitignore files in the repository tree or from a shared system file+ * only if a "core.excludesfile" config value is set. The library also+ * keeps a set of per-repository internal ignores that can be configured+ * in-memory and will not persist. This function allows you to add to+ * that internal rules list.+ *+ * Example usage:+ *+ * error = git_ignore_add_rule(myrepo, "*.c\ndir/\nFile with space\n");+ *+ * This would add three rules to the ignores.+ *+ * @param repo The repository to add ignore rules to.+ * @param rules Text of rules, a la the contents of a .gitignore file.+ * It is okay to have multiple rules in the text; if so,+ * each rule should be terminated with a newline.+ * @return 0 on success+ */+GIT_EXTERN(int) git_ignore_add_rule(+ git_repository *repo,+ const char *rules);++/**+ * Clear ignore rules that were explicitly added.+ *+ * Resets to the default internal ignore rules. This will not turn off+ * rules in .gitignore files that actually exist in the filesystem.+ *+ * The default internal ignores ignore ".", ".." and ".git" entries.+ *+ * @param repo The repository to remove ignore rules from.+ * @return 0 on success+ */+GIT_EXTERN(int) git_ignore_clear_internal_rules(+ git_repository *repo);++/**+ * Test if the ignore rules apply to a given path.+ *+ * This function checks the ignore rules to see if they would apply to the+ * given file. This indicates if the file would be ignored regardless of+ * whether the file is already in the index or committed to the repository.+ *+ * One way to think of this is if you were to do "git add ." on the+ * directory containing the file, would it be added or not?+ *+ * @param ignored boolean returning 0 if the file is not ignored, 1 if it is+ * @param repo a repository object+ * @param path the file to check ignores for, relative to the repo's workdir.+ * @return 0 if ignore rules could be processed for the file (regardless+ * of whether it exists or not), or an error < 0 if they could not.+ */+GIT_EXTERN(int) git_ignore_path_is_ignored(+ int *ignored,+ git_repository *repo,+ const char *path);++GIT_END_DECL++#endif
libgit2/include/git2/index.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,6 +8,7 @@ #define INCLUDE_git_index_h__ #include "common.h"+#include "indexer.h" #include "types.h" #include "oid.h" @@ -20,10 +21,10 @@ */ GIT_BEGIN_DECL -#define GIT_IDXENTRY_NAMEMASK (0x0fff)+#define GIT_IDXENTRY_NAMEMASK (0x0fff) #define GIT_IDXENTRY_STAGEMASK (0x3000)-#define GIT_IDXENTRY_EXTENDED (0x4000)-#define GIT_IDXENTRY_VALID (0x8000)+#define GIT_IDXENTRY_EXTENDED (0x4000)+#define GIT_IDXENTRY_VALID (0x8000) #define GIT_IDXENTRY_STAGESHIFT 12 /*@@ -33,26 +34,26 @@ * * In-memory only flags: */-#define GIT_IDXENTRY_UPDATE (1 << 0)-#define GIT_IDXENTRY_REMOVE (1 << 1)-#define GIT_IDXENTRY_UPTODATE (1 << 2)-#define GIT_IDXENTRY_ADDED (1 << 3)+#define GIT_IDXENTRY_UPDATE (1 << 0)+#define GIT_IDXENTRY_REMOVE (1 << 1)+#define GIT_IDXENTRY_UPTODATE (1 << 2)+#define GIT_IDXENTRY_ADDED (1 << 3) -#define GIT_IDXENTRY_HASHED (1 << 4)-#define GIT_IDXENTRY_UNHASHED (1 << 5)-#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */-#define GIT_IDXENTRY_CONFLICTED (1 << 7)+#define GIT_IDXENTRY_HASHED (1 << 4)+#define GIT_IDXENTRY_UNHASHED (1 << 5)+#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */+#define GIT_IDXENTRY_CONFLICTED (1 << 7) -#define GIT_IDXENTRY_UNPACKED (1 << 8)+#define GIT_IDXENTRY_UNPACKED (1 << 8) #define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9) /* * Extended on-disk flags: */-#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13)-#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14)+#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13)+#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14) /* GIT_IDXENTRY_EXTENDED2 is for future extension */-#define GIT_IDXENTRY_EXTENDED2 (1 << 15)+#define GIT_IDXENTRY_EXTENDED2 (1 << 15) #define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) @@ -83,13 +84,27 @@ char *path; } git_index_entry; -/** Representation of an unmerged file entry in the index. */-typedef struct git_index_entry_unmerged {+/** Representation of a resolve undo entry in the index. */+typedef struct git_index_reuc_entry { unsigned int mode[3]; git_oid oid[3]; char *path;-} git_index_entry_unmerged;+} git_index_reuc_entry; +/** Capabilities of system that affect index actions. */+enum {+ GIT_INDEXCAP_IGNORE_CASE = 1,+ GIT_INDEXCAP_NO_FILEMODE = 2,+ GIT_INDEXCAP_NO_SYMLINKS = 4,+ GIT_INDEXCAP_FROM_OWNER = ~0u+};++/** @name Index File Functions+ *+ * These functions work on the index file itself.+ */+/**@{*/+ /** * Create a new bare Git index object as a memory representation * of the Git index file in 'index_path', without a repository@@ -104,20 +119,24 @@ * * The index must be freed once it's no longer in use. *- * @param index the pointer for the new index+ * @param out the pointer for the new index * @param index_path the path to the index file in disk * @return 0 or an error code */-GIT_EXTERN(int) git_index_open(git_index **index, const char *index_path);+GIT_EXTERN(int) git_index_open(git_index **out, const char *index_path); /**- * Clear the contents (all the entries) of an index object.- * This clears the index object in memory; changes must be manually- * written to disk for them to take effect.+ * Create an in-memory index object. *- * @param index an existing index object+ * This index object cannot be read/written to the filesystem,+ * but may be used to perform in-memory index operations.+ *+ * The index must be freed once it's no longer in use.+ *+ * @param out the pointer for the new index+ * @return 0 or an error code */-GIT_EXTERN(void) git_index_clear(git_index *index);+GIT_EXTERN(int) git_index_new(git_index **out); /** * Free an existing index object.@@ -127,6 +146,35 @@ GIT_EXTERN(void) git_index_free(git_index *index); /**+ * Get the repository this index relates to+ *+ * @param index The index+ * @return A pointer to the repository+ */+GIT_EXTERN(git_repository *) git_index_owner(const git_index *index);++/**+ * Read index capabilities flags.+ *+ * @param index An existing index object+ * @return A combination of GIT_INDEXCAP values+ */+GIT_EXTERN(unsigned int) git_index_caps(const git_index *index);++/**+ * Set index capabilities flags.+ *+ * If you pass `GIT_INDEXCAP_FROM_OWNER` for the caps, then the+ * capabilities will be read from the config of the owner object,+ * looking at `core.ignorecase`, `core.filemode`, `core.symlinks`.+ *+ * @param index An existing index object+ * @param caps A combination of GIT_INDEXCAP values+ * @return 0 on success, -1 on failure+ */+GIT_EXTERN(int) git_index_set_caps(git_index *index, unsigned int caps);++/** * Update the contents of an existing index object in memory * by reading from the hard disk. *@@ -145,44 +193,143 @@ GIT_EXTERN(int) git_index_write(git_index *index); /**- * Find the first index of any entries which point to given- * path in the Git index.+ * Read a tree into the index file with stats *+ * The current index contents will be replaced by the specified tree.+ * * @param index an existing index object- * @param path path to search- * @return an index >= 0 if found, -1 otherwise+ * @param tree tree to read+ * @return 0 or an error code */-GIT_EXTERN(int) git_index_find(git_index *index, const char *path);+GIT_EXTERN(int) git_index_read_tree(git_index *index, const git_tree *tree); /**- * Remove all entries with equal path except last added+ * Write the index as a tree *+ * This method will scan the index and write a representation+ * of its current state back to disk; it recursively creates+ * tree objects for each of the subtrees stored in the index,+ * but only returns the OID of the root tree. This is the OID+ * that can be used e.g. to create a commit.+ *+ * The index instance cannot be bare, and needs to be associated+ * to an existing repository.+ *+ * The index must not contain any file in conflict.+ *+ * @param out Pointer where to store the OID of the written tree+ * @param index Index to write+ * @return 0 on success, GIT_EUNMERGED when the index is not clean+ * or an error code+ */+GIT_EXTERN(int) git_index_write_tree(git_oid *out, git_index *index);++/**+ * Write the index as a tree to the given repository+ *+ * This method will do the same as `git_index_write_tree`, but+ * letting the user choose the repository where the tree will+ * be written.+ *+ * The index must not contain any file in conflict.+ *+ * @param out Pointer where to store OID of the the written tree+ * @param index Index to write+ * @param repo Repository where to write the tree+ * @return 0 on success, GIT_EUNMERGED when the index is not clean+ * or an error code+ */+GIT_EXTERN(int) git_index_write_tree_to(git_oid *out, git_index *index, git_repository *repo);++/**@}*/++/** @name Raw Index Entry Functions+ *+ * These functions work on index entries, and allow for raw manipulation+ * of the entries.+ */+/**@{*/++/* Index entry manipulation */++/**+ * Get the count of entries currently in the index+ * * @param index an existing index object+ * @return integer of count of current entries */-GIT_EXTERN(void) git_index_uniq(git_index *index);+GIT_EXTERN(size_t) git_index_entrycount(const git_index *index); /**- * Add or update an index entry from a file in disk+ * Clear the contents (all the entries) of an index object.+ * This clears the index object in memory; changes must be manually+ * written to disk for them to take effect. *- * The file `path` must be relative to the repository's- * working folder and must be readable.+ * @param index an existing index object+ */+GIT_EXTERN(void) git_index_clear(git_index *index);++/**+ * Get a pointer to one of the entries in the index *- * This method will fail in bare index instances.+ * The values of this entry can be modified (except the path)+ * and the changes will be written back to disk on the next+ * write() call. *- * This forces the file to be added to the index, not looking- * at gitignore rules. Those rules can be evaluated through- * the git_status APIs (in status.h) before calling this.+ * The entry should not be freed by the caller. * * @param index an existing index object- * @param path filename to add- * @param stage stage for the entry+ * @param n the position of the entry+ * @return a pointer to the entry; NULL if out of bounds+ */+GIT_EXTERN(const git_index_entry *) git_index_get_byindex(+ git_index *index, size_t n);++/**+ * Get a pointer to one of the entries in the index+ *+ * The values of this entry can be modified (except the path)+ * and the changes will be written back to disk on the next+ * write() call.+ *+ * The entry should not be freed by the caller.+ *+ * @param index an existing index object+ * @param path path to search+ * @param stage stage to search+ * @return a pointer to the entry; NULL if it was not found+ */+GIT_EXTERN(const git_index_entry *) git_index_get_bypath(+ git_index *index, const char *path, int stage);++/**+ * Remove an entry from the index+ *+ * @param index an existing index object+ * @param path path to search+ * @param stage stage to search * @return 0 or an error code */-GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage);+GIT_EXTERN(int) git_index_remove(git_index *index, const char *path, int stage); /**+ * Remove all entries from the index under a given directory+ *+ * @param index an existing index object+ * @param dir container directory path+ * @param stage stage to search+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_index_remove_directory(+ git_index *index, const char *dir, int stage);++/** * Add or update an index entry from an in-memory struct *+ * If a previous index entry exists that has the same path and stage+ * as the given 'source_entry', it will be replaced. Otherwise, the+ * 'source_entry' will be added.+ * * A full copy (including the 'path' string) of the given * 'source_entry' will be inserted on the index. *@@ -190,131 +337,242 @@ * @param source_entry new entry object * @return 0 or an error code */-GIT_EXTERN(int) git_index_add2(git_index *index, const git_index_entry *source_entry);+GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_entry); /**- * Add (append) an index entry from a file in disk+ * Return the stage number from a git index entry *- * A new entry will always be inserted into the index;- * if the index already contains an entry for such- * path, the old entry will **not** be replaced.+ * This entry is calculated from the entry's flag+ * attribute like this: *+ * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT+ *+ * @param entry The entry+ * @returns the stage number+ */+GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);++/**@}*/++/** @name Workdir Index Entry Functions+ *+ * These functions work on index entries specifically in the working+ * directory (ie, stage 0).+ */+/**@{*/++/**+ * Add or update an index entry from a file on disk+ * * The file `path` must be relative to the repository's * working folder and must be readable. * * This method will fail in bare index instances. *+ * This forces the file to be added to the index, not looking+ * at gitignore rules. Those rules can be evaluated through+ * the git_status APIs (in status.h) before calling this.+ *+ * If this file currently is the result of a merge conflict, this+ * file will no longer be marked as conflicting. The data about+ * the conflict will be moved to the "resolve undo" (REUC) section.+ * * @param index an existing index object * @param path filename to add- * @param stage stage for the entry * @return 0 or an error code */-GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage);+GIT_EXTERN(int) git_index_add_bypath(git_index *index, const char *path); /**- * Add (append) an index entry from an in-memory struct+ * Remove an index entry corresponding to a file on disk *- * A new entry will always be inserted into the index;- * if the index already contains an entry for the path- * in the `entry` struct, the old entry will **not** be- * replaced.+ * The file `path` must be relative to the repository's+ * working folder. It may exist. *- * A full copy (including the 'path' string) of the given- * 'source_entry' will be inserted on the index.+ * If this file currently is the result of a merge conflict, this+ * file will no longer be marked as conflicting. The data about+ * the conflict will be moved to the "resolve undo" (REUC) section. * * @param index an existing index object- * @param source_entry new entry object+ * @param path filename to remove * @return 0 or an error code */-GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *source_entry);+GIT_EXTERN(int) git_index_remove_bypath(git_index *index, const char *path); /**- * Remove an entry from the index+ * Find the first position of any entries which point to given+ * path in the Git index. *+ * @param at_pos the address to which the position of the index entry is written (optional) * @param index an existing index object- * @param position position of the entry to remove- * @return 0 or an error code+ * @param path path to search+ * @return a zero-based position in the index if found;+ * GIT_ENOTFOUND otherwise */-GIT_EXTERN(int) git_index_remove(git_index *index, int position);+GIT_EXTERN(int) git_index_find(size_t *at_pos, git_index *index, const char *path); +/**@}*/ +/** @name Conflict Index Entry Functions+ *+ * These functions work on conflict index entries specifically (ie, stages 1-3)+ */+/**@{*/+ /**- * Get a pointer to one of the entries in the index+ * Add or update index entries to represent a conflict *- * This entry can be modified, and the changes will be written- * back to disk on the next write() call.+ * The entries are the entries from the tree included in the merge. Any+ * entry may be null to indicate that that file was not present in the+ * trees during the merge. For example, ancestor_entry may be NULL to+ * indicate that a file was added in both branches and must be resolved. *- * The entry should not be freed by the caller.+ * @param index an existing index object+ * @param ancestor_entry the entry data for the ancestor of the conflict+ * @param our_entry the entry data for our side of the merge conflict+ * @param their_entry the entry data for their side of the merge conflict+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_index_conflict_add(+ git_index *index,+ const git_index_entry *ancestor_entry,+ const git_index_entry *our_entry,+ const git_index_entry *their_entry);++/**+ * Get the index entries that represent a conflict of a single file. *+ * The values of this entry can be modified (except the paths)+ * and the changes will be written back to disk on the next+ * write() call.+ *+ * @param ancestor_out Pointer to store the ancestor entry+ * @param our_out Pointer to store the our entry+ * @param their_out Pointer to store the their entry * @param index an existing index object- * @param n the position of the entry- * @return a pointer to the entry; NULL if out of bounds+ * @param path path to search */-GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, unsigned int n);+GIT_EXTERN(int) git_index_conflict_get(git_index_entry **ancestor_out, git_index_entry **our_out, git_index_entry **their_out, git_index *index, const char *path); /**- * Get the count of entries currently in the index+ * Removes the index entries that represent a conflict of a single file. * * @param index an existing index object- * @return integer of count of current entries+ * @param path to search */-GIT_EXTERN(unsigned int) git_index_entrycount(git_index *index);+GIT_EXTERN(int) git_index_conflict_remove(git_index *index, const char *path); /**- * Get the count of unmerged entries currently in the index+ * Remove all conflicts in the index (entries with a stage greater than 0.) * * @param index an existing index object- * @return integer of count of current unmerged entries */-GIT_EXTERN(unsigned int) git_index_entrycount_unmerged(git_index *index);+GIT_EXTERN(void) git_index_conflict_cleanup(git_index *index); /**- * Get an unmerged entry from the index.+ * Determine if the index contains entries representing file conflicts. *+ * @return 1 if at least one conflict is found, 0 otherwise.+ */+GIT_EXTERN(int) git_index_has_conflicts(const git_index *index);++/**@}*/++/** @name Resolve Undo (REUC) index entry manipulation.+ *+ * These functions work on the Resolve Undo index extension and contains+ * data about the original files that led to a merge conflict.+ */+/**@{*/++/**+ * Get the count of resolve undo entries currently in the index.+ *+ * @param index an existing index object+ * @return integer of count of current resolve undo entries+ */+GIT_EXTERN(unsigned int) git_index_reuc_entrycount(git_index *index);++/**+ * Finds the resolve undo entry that points to the given path in the Git+ * index.+ *+ * @param at_pos the address to which the position of the reuc entry is written (optional)+ * @param index an existing index object+ * @param path path to search+ * @return 0 if found, < 0 otherwise (GIT_ENOTFOUND)+ */+GIT_EXTERN(int) git_index_reuc_find(size_t *at_pos, git_index *index, const char *path);++/**+ * Get a resolve undo entry from the index.+ * * The returned entry is read-only and should not be modified- * of freed by the caller.+ * or freed by the caller. * * @param index an existing index object * @param path path to search- * @return the unmerged entry; NULL if not found+ * @return the resolve undo entry; NULL if not found */-GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_bypath(git_index *index, const char *path);+GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_bypath(git_index *index, const char *path); /**- * Get an unmerged entry from the index.+ * Get a resolve undo entry from the index. * * The returned entry is read-only and should not be modified- * of freed by the caller.+ * or freed by the caller. * * @param index an existing index object * @param n the position of the entry- * @return a pointer to the unmerged entry; NULL if out of bounds+ * @return a pointer to the resolve undo entry; NULL if out of bounds */-GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_index *index, unsigned int n);+GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_byindex(git_index *index, size_t n); /**- * Return the stage number from a git index entry+ * Adds a resolve undo entry for a file based on the given parameters. *- * This entry is calculated from the entrie's flag- * attribute like this:+ * The resolve undo entry contains the OIDs of files that were involved+ * in a merge conflict after the conflict has been resolved. This allows+ * conflicts to be re-resolved later. *- * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT+ * If there exists a resolve undo entry for the given path in the index,+ * it will be removed. *- * @param entry The entry- * @returns the stage number+ * This method will fail in bare index instances.+ *+ * @param index an existing index object+ * @param path filename to add+ * @param ancestor_mode mode of the ancestor file+ * @param ancestor_id oid of the ancestor file+ * @param our_mode mode of our file+ * @param our_id oid of our file+ * @param their_mode mode of their file+ * @param their_id oid of their file+ * @return 0 or an error code */-GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);+GIT_EXTERN(int) git_index_reuc_add(git_index *index, const char *path,+ int ancestor_mode, git_oid *ancestor_id,+ int our_mode, git_oid *our_id,+ int their_mode, git_oid *their_id); /**- * Read a tree into the index file+ * Remove an resolve undo entry from the index *- * The current index contents will be replaced by the specified tree.+ * @param index an existing index object+ * @param n position of the resolve undo entry to remove+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_index_reuc_remove(git_index *index, size_t n);++/**+ * Remove all resolve undo entries from the index * * @param index an existing index object- * @param tree tree to read * @return 0 or an error code */-GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree);+GIT_EXTERN(void) git_index_reuc_clear(git_index *index);++/**@}*/ /** @} */ GIT_END_DECL
libgit2/include/git2/indexer.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -16,32 +16,48 @@ * This is passed as the first argument to the callback to allow the * user to see the progress. */-typedef struct git_indexer_stats {- unsigned int total;- unsigned int processed;-} git_indexer_stats;+typedef struct git_transfer_progress {+ unsigned int total_objects;+ unsigned int indexed_objects;+ unsigned int received_objects;+ size_t received_bytes;+} git_transfer_progress; -typedef struct git_indexer git_indexer;+/**+ * Type for progress callbacks during indexing. Return a value less than zero+ * to cancel the transfer.+ *+ * @param stats Structure containing information about the state of the transfer+ * @param payload Payload provided by caller+ */+typedef int (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload);+ typedef struct git_indexer_stream git_indexer_stream; /** * Create a new streaming indexer instance *- * @param out where to store the inexer instance- * @param path to the gitdir (metadata directory)+ * @param out where to store the indexer instance+ * @param path to the directory where the packfile should be stored+ * @param progress_cb function to call with progress information+ * @param progress_payload payload for the progress callback */-GIT_EXTERN(int) git_indexer_stream_new(git_indexer_stream **out, const char *gitdir);+GIT_EXTERN(int) git_indexer_stream_new(+ git_indexer_stream **out,+ const char *path,+ git_transfer_progress_callback progress_cb,+ void *progress_cb_payload); /** * Add data to the indexer * * @param idx the indexer * @param data the data to add- * @param size the size of the data+ * @param size the size of the data in bytes * @param stats stat storage */-GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats);+GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats); /** * Finalize the pack and index@@ -50,7 +66,7 @@ * * @param idx the indexer */-GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats);+GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats); /** * Get the packfile's hash@@ -60,7 +76,7 @@ * * @param idx the indexer instance */-GIT_EXTERN(const git_oid *) git_indexer_stream_hash(git_indexer_stream *idx);+GIT_EXTERN(const git_oid *) git_indexer_stream_hash(const git_indexer_stream *idx); /** * Free the indexer and its resources@@ -68,53 +84,6 @@ * @param idx the indexer to free */ GIT_EXTERN(void) git_indexer_stream_free(git_indexer_stream *idx);--/**- * Create a new indexer instance- *- * @param out where to store the indexer instance- * @param packname the absolute filename of the packfile to index- */-GIT_EXTERN(int) git_indexer_new(git_indexer **out, const char *packname);--/**- * Iterate over the objects in the packfile and extract the information- *- * Indexing a packfile can be very expensive so this function is- * expected to be run in a worker thread and the stats used to provide- * feedback the user.- *- * @param idx the indexer instance- * @param stats storage for the running state- */-GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_indexer_stats *stats);--/**- * Write the index file to disk.- *- * The file will be stored as pack-$hash.idx in the same directory as- * the packfile.- *- * @param idx the indexer instance- */-GIT_EXTERN(int) git_indexer_write(git_indexer *idx);--/**- * Get the packfile's hash- *- * A packfile's name is derived from the sorted hashing of all object- * names. This is only correct after the index has been written to disk.- *- * @param idx the indexer instance- */-GIT_EXTERN(const git_oid *) git_indexer_hash(git_indexer *idx);--/**- * Free the indexer and its resources- *- * @param idx the indexer to free- */-GIT_EXTERN(void) git_indexer_free(git_indexer *idx); GIT_END_DECL
libgit2/include/git2/inttypes.h view
@@ -40,7 +40,11 @@ #pragma once #endif +#if _MSC_VER >= 1600+#include <stdint.h>+#else #include "stdint.h"+#endif // 7.8 Format conversion of integer types
libgit2/include/git2/merge.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -27,8 +27,28 @@ * @param repo the repository where the commits exist * @param one one of the commits * @param two the other commit+ * @return Zero on success; GIT_ENOTFOUND or -1 on failure. */-GIT_EXTERN(int) git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two);+GIT_EXTERN(int) git_merge_base(+ git_oid *out,+ git_repository *repo,+ const git_oid *one,+ const git_oid *two);++/**+ * Find a merge base given a list of commits+ *+ * @param out the OID of a merge base considering all the commits+ * @param repo the repository where the commits exist+ * @param input_array oids of the commits+ * @param length The number of commits in the provided `input_array`+ * @return Zero on success; GIT_ENOTFOUND or -1 on failure.+ */+GIT_EXTERN(int) git_merge_base_many(+ git_oid *out,+ git_repository *repo,+ const git_oid input_array[],+ size_t length); /** @} */ GIT_END_DECL
+ libgit2/include/git2/message.h view
@@ -0,0 +1,49 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_message_h__+#define INCLUDE_git_message_h__++#include "common.h"++/**+ * @file git2/message.h+ * @brief Git message management routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Clean up message from excess whitespace and make sure that the last line+ * ends with a '\n'.+ *+ * Optionally, can remove lines starting with a "#".+ *+ * @param out The user-allocated buffer which will be filled with the+ * cleaned up message. Pass NULL if you just want to get the needed+ * size of the prettified message as the output value.+ *+ * @param out_size Size of the `out` buffer in bytes.+ *+ * @param message The message to be prettified.+ *+ * @param strip_comments Non-zero to remove lines starting with "#", 0 to+ * leave them in.+ *+ * @return -1 on error, else number of characters in prettified message+ * including the trailing NUL byte+ */+GIT_EXTERN(int) git_message_prettify(+ char *out,+ size_t out_size,+ const char *message,+ int strip_comments);++/** @} */+GIT_END_DECL++#endif /* INCLUDE_git_message_h__ */
libgit2/include/git2/net.h view
@@ -1,11 +1,11 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */-#ifndef INCLUDE_net_h__-#define INCLUDE_net_h__+#ifndef INCLUDE_git_net_h__+#define INCLUDE_git_net_h__ #include "common.h" #include "oid.h"@@ -27,15 +27,17 @@ * gets called. */ -#define GIT_DIR_FETCH 0-#define GIT_DIR_PUSH 1+typedef enum {+ GIT_DIRECTION_FETCH = 0,+ GIT_DIRECTION_PUSH = 1+} git_direction; /** * Remote head description, given out on `ls` calls. */ struct git_remote_head {- int local:1; /* available locally */+ int local; /* available locally */ git_oid oid; git_oid loid; char *name;@@ -44,7 +46,7 @@ /** * Callback for listing the remote heads */-typedef int (*git_headlist_cb)(git_remote_head *, void *);+typedef int (*git_headlist_cb)(git_remote_head *rhead, void *payload); /** @} */ GIT_END_DECL
libgit2/include/git2/notes.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -19,19 +19,82 @@ GIT_BEGIN_DECL /**+ * Callback for git_note_foreach.+ *+ * Receives:+ * - blob_id: Oid of the blob containing the message+ * - annotated_object_id: Oid of the git object being annotated+ * - payload: Payload data passed to `git_note_foreach`+ */+typedef int (*git_note_foreach_cb)(+ const git_oid *blob_id, const git_oid *annotated_object_id, void *payload);++/**+ * note iterator+ */+typedef struct git_iterator git_note_iterator;++/**+ * Creates a new iterator for notes+ *+ * The iterator must be freed manually by the user.+ *+ * @param out pointer to the iterator+ * @param repo repository where to look up the note+ * @param notes_ref canonical name of the reference to use (optional); defaults to+ * "refs/notes/commits"+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_note_iterator_new(+ git_note_iterator **out,+ git_repository *repo,+ const char *notes_ref);++/**+ * Frees an git_note_iterator+ *+ * @param it pointer to the iterator+ */+GIT_EXTERN(void) git_note_iterator_free(git_note_iterator *it);++/**+ * Returns the current item (note_id and annotated_id) and advance the iterator+ * internally to the next value+ *+ * The notes must not be freed manually by the user.+ *+ * @param it pointer to the iterator+ * @param note_id id of blob containing the message+ * @param annotated_id id of the git object being annotated+ *+ * @return 0 (no error), GIT_ITEROVER (iteration is done) or an error code+ * (negative value)+ */+GIT_EXTERN(int) git_note_next(+ git_oid* note_id,+ git_oid* annotated_id,+ git_note_iterator *it);+++/** * Read the note for an object * * The note must be freed manually by the user. *- * @param note the note; NULL in case of error- * @param repo the Git repository- * @param notes_ref OID reference to use (optional); defaults to "refs/notes/commits"- * @param oid OID of the object+ * @param out pointer to the read note; NULL in case of error+ * @param repo repository where to look up the note+ * @param notes_ref canonical name of the reference to use (optional); defaults to+ * "refs/notes/commits"+ * @param oid OID of the git object to read the note from * * @return 0 or an error code */-GIT_EXTERN(int) git_note_read(git_note **note, git_repository *repo,- const char *notes_ref, const git_oid *oid);+GIT_EXTERN(int) git_note_read(+ git_note **out,+ git_repository *repo,+ const char *notes_ref,+ const git_oid *oid); /** * Get the note message@@ -39,7 +102,7 @@ * @param note * @return the note message */-GIT_EXTERN(const char *) git_note_message(git_note *note);+GIT_EXTERN(const char *) git_note_message(const git_note *note); /**@@ -48,42 +111,52 @@ * @param note * @return the note object OID */-GIT_EXTERN(const git_oid *) git_note_oid(git_note *note);-+GIT_EXTERN(const git_oid *) git_note_oid(const git_note *note); /** * Add a note for an object *- * @param oid pointer to store the OID (optional); NULL in case of error- * @param repo the Git repository+ * @param out pointer to store the OID (optional); NULL in case of error+ * @param repo repository where to store the note * @param author signature of the notes commit author * @param committer signature of the notes commit committer- * @param notes_ref OID reference to update (optional); defaults to "refs/notes/commits"- * @param oid The OID of the object- * @param oid The note to add for object oid+ * @param notes_ref canonical name of the reference to use (optional);+ * defaults to "refs/notes/commits"+ * @param oid OID of the git object to decorate+ * @param note Content of the note to add for object oid+ * @param force Overwrite existing note * * @return 0 or an error code */-GIT_EXTERN(int) git_note_create(git_oid *out, git_repository *repo,- git_signature *author, git_signature *committer,- const char *notes_ref, const git_oid *oid,- const char *note);+GIT_EXTERN(int) git_note_create(+ git_oid *out,+ git_repository *repo,+ const git_signature *author,+ const git_signature *committer,+ const char *notes_ref,+ const git_oid *oid,+ const char *note,+ int force); /** * Remove the note for an object *- * @param repo the Git repository- * @param notes_ref OID reference to use (optional); defaults to "refs/notes/commits"+ * @param repo repository where the note lives+ * @param notes_ref canonical name of the reference to use (optional);+ * defaults to "refs/notes/commits" * @param author signature of the notes commit author * @param committer signature of the notes commit committer- * @param oid the oid which note's to be removed+ * @param oid OID of the git object to remove the note from * * @return 0 or an error code */-GIT_EXTERN(int) git_note_remove(git_repository *repo, const char *notes_ref,- git_signature *author, git_signature *committer,- const git_oid *oid);+GIT_EXTERN(int) git_note_remove(+ git_repository *repo,+ const char *notes_ref,+ const git_signature *author,+ const git_signature *committer,+ const git_oid *oid); /** * Free a git_note object@@ -103,36 +176,26 @@ GIT_EXTERN(int) git_note_default_ref(const char **out, git_repository *repo); /**- * Basic components of a note- *- * - Oid of the blob containing the message- * - Oid of the git object being annotated- */-typedef struct {- git_oid blob_oid;- git_oid annotated_object_oid;-} git_note_data;--/** * Loop over all the notes within a specified namespace * and issue a callback for each one. * * @param repo Repository where to find the notes. *- * @param notes_ref OID reference to read from (optional); defaults to "refs/notes/commits".+ * @param notes_ref Reference to read from (optional); defaults to+ * "refs/notes/commits". *- * @param note_cb Callback to invoke per found annotation.+ * @param note_cb Callback to invoke per found annotation. Return non-zero+ * to stop looping. * * @param payload Extra parameter to callback function. *- * @return 0 or an error code.+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_note_foreach(- git_repository *repo,- const char *notes_ref,- int (*note_cb)(git_note_data *note_data, void *payload),- void *payload-);+ git_repository *repo,+ const char *notes_ref,+ git_note_foreach_cb note_cb,+ void *payload); /** @} */ GIT_END_DECL
libgit2/include/git2/object.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -21,7 +21,7 @@ GIT_BEGIN_DECL /**- * Lookup a reference to one of the objects in a repostory.+ * Lookup a reference to one of the objects in a repository. * * The generated reference is owned by the repository and * should be closed with the `git_object_free` method@@ -45,7 +45,7 @@ git_otype type); /**- * Lookup a reference to one of the objects in a repostory,+ * Lookup a reference to one of the objects in a repository, * given a prefix of its identifier (short id). * * The object obtained will be so that its identifier@@ -75,7 +75,7 @@ git_object **object_out, git_repository *repo, const git_oid *id,- unsigned int len,+ size_t len, git_otype type); /**@@ -114,7 +114,7 @@ * This method instructs the library to close an existing * object; note that git_objects are owned and cached by the repository * so the object may or may not be freed after this library call,- * depending on how agressive is the caching mechanism used+ * depending on how aggressive is the caching mechanism used * by the repository. * * IMPORTANT:@@ -166,6 +166,36 @@ * @return size in bytes of the object */ GIT_EXTERN(size_t) git_object__size(git_otype type);++/**+ * Recursively peel an object until an object of the specified type is met.+ *+ * The retrieved `peeled` object is owned by the repository and should be+ * closed with the `git_object_free` method.+ *+ * If you pass `GIT_OBJ_ANY` as the target type, then the object will be+ * peeled until the type changes (e.g. a tag will be chased until the+ * referenced object is no longer a tag).+ *+ * @param peeled Pointer to the peeled git_object+ * @param object The object to be processed+ * @param target_type The type of the requested object (GIT_OBJ_COMMIT,+ * GIT_OBJ_TAG, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_ANY).+ * @return 0 on success, GIT_EAMBIGUOUS, GIT_ENOTFOUND or an error code+ */+GIT_EXTERN(int) git_object_peel(+ git_object **peeled,+ const git_object *object,+ git_otype target_type);++/**+ * Create an in-memory copy of a Git object. The copy must be+ * explicitly free'd or it will leak.+ *+ * @param dest Pointer to store the copy of the object+ * @param source Original object to copy+ */+GIT_EXTERN(int) git_object_dup(git_object **dest, git_object *source); /** @} */ GIT_END_DECL
libgit2/include/git2/odb.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -11,6 +11,7 @@ #include "types.h" #include "oid.h" #include "odb_backend.h"+#include "indexer.h" /** * @file git2/odb.h@@ -62,7 +63,7 @@ * @param odb database to add the backend to * @param backend pointer to a git_odb_backend instance * @param priority Value for ordering the backends queue- * @return 0 on sucess; error code otherwise+ * @return 0 on success; error code otherwise */ GIT_EXTERN(int) git_odb_add_backend(git_odb *odb, git_odb_backend *backend, int priority); @@ -83,11 +84,28 @@ * @param odb database to add the backend to * @param backend pointer to a git_odb_backend instance * @param priority Value for ordering the backends queue- * @return 0 on sucess; error code otherwise+ * @return 0 on success; error code otherwise */ GIT_EXTERN(int) git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority); /**+ * Add an on-disk alternate to an existing Object DB.+ *+ * Note that the added path must point to an `objects`, not+ * to a full repository, to use it as an alternate store.+ *+ * Alternate backends are always checked for objects *after*+ * all the main backends have been exhausted.+ *+ * Writing is disabled on alternate backends.+ *+ * @param odb database to add the backend to+ * @param path path to the objects folder for the alternate+ * @return 0 on success; error code otherwise+ */+GIT_EXTERN(int) git_odb_add_disk_alternate(git_odb *odb, const char *path);++/** * Close an open object database. * * @param db database pointer to close. If NULL no action is taken.@@ -135,11 +153,12 @@ * @param db database to search for the object in. * @param short_id a prefix of the id of the object to read. * @param len the length of the prefix- * @return 0 if the object was read;- * GIT_ENOTFOUND if the object is not in the database.- * GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix)+ * @return + * - 0 if the object was read;+ * - GIT_ENOTFOUND if the object is not in the database.+ * - GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix) */-GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len);+GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len); /** * Read the header of an object from the database, without@@ -151,15 +170,15 @@ * of an object, so the whole object will be read and then the * header will be returned. *- * @param len_p pointer where to store the length- * @param type_p pointer where to store the type+ * @param len_out pointer where to store the length+ * @param type_out pointer where to store the type * @param db database to search for the object in. * @param id identity of the object to read. * @return * - 0 if the object was read; * - GIT_ENOTFOUND if the object is not in the database. */-GIT_EXTERN(int) git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id);+GIT_EXTERN(int) git_odb_read_header(size_t *len_out, git_otype *type_out, git_odb *db, const git_oid *id); /** * Determine if the given object can be found in the object database.@@ -173,6 +192,41 @@ GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id); /**+ * Refresh the object database to load newly added files.+ *+ * If the object databases have changed on disk while the library+ * is running, this function will force a reload of the underlying+ * indexes.+ *+ * Use this function when you're confident that an external+ * application has tampered with the ODB.+ *+ * NOTE that it is not necessary to call this function at all. The+ * library will automatically attempt to refresh the ODB+ * when a lookup fails, to see if the looked up object exists+ * on disk but hasn't been loaded yet.+ *+ * @param db database to refresh+ * @return 0 on success, error code otherwise+ */+GIT_EXTERN(int) git_odb_refresh(struct git_odb *db);++/**+ * List all objects available in the database+ *+ * The callback will be called for each object available in the+ * database. Note that the objects are likely to be returned in the index+ * order, which would make accessing the objects in that order inefficient.+ * Return a non-zero value from the callback to stop looping.+ *+ * @param db database to use+ * @param cb the callback to call for each object+ * @param payload data to pass to the callback+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code+ */+GIT_EXTERN(int) git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload);++/** * Write an object directly into the ODB * * This method writes a full object straight into the ODB.@@ -183,14 +237,14 @@ * This method is provided for compatibility with custom backends * which are not able to support streaming writes *- * @param oid pointer to store the OID result of the write+ * @param out pointer to store the OID result of the write * @param odb object database where to store the object- * @param data buffer with the data to storr+ * @param data buffer with the data to store * @param len size of the buffer * @param type type of the data to store * @return 0 or an error code */-GIT_EXTERN(int) git_odb_write(git_oid *oid, git_odb *odb, const void *data, size_t len, git_otype type);+GIT_EXTERN(int) git_odb_write(git_oid *out, git_odb *odb, const void *data, size_t len, git_otype type); /** * Open a stream to write an object into the ODB@@ -213,13 +267,13 @@ * * @see git_odb_stream *- * @param stream pointer where to store the stream+ * @param out pointer where to store the stream * @param db object database where the stream will write * @param size final size of the object that will be written * @param type type of the object that will be written * @return 0 if the stream was created; error code otherwise */-GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_t size, git_otype type);+GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **out, git_odb *db, size_t size, git_otype type); /** * Open a stream to read an object from the ODB@@ -240,32 +294,58 @@ * * @see git_odb_stream *- * @param stream pointer where to store the stream+ * @param out pointer where to store the stream * @param db object database where the stream will read from * @param oid oid of the object the stream will read from * @return 0 if the stream was created; error code otherwise */-GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid);+GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **out, git_odb *db, const git_oid *oid); /**+ * Open a stream for writing a pack file to the ODB.+ *+ * If the ODB layer understands pack files, then the given+ * packfile will likely be streamed directly to disk (and a+ * corresponding index created). If the ODB layer does not+ * understand pack files, the objects will be stored in whatever+ * format the ODB layer uses.+ *+ * @see git_odb_writepack+ *+ * @param out pointer to the writepack functions+ * @param db object database where the stream will read from+ * @param progress_cb function to call with progress information.+ * Be aware that this is called inline with network and indexing operations,+ * so performance may be affected.+ * @param progress_payload payload for the progress callback+ */+GIT_EXTERN(int) git_odb_write_pack(+ git_odb_writepack **out,+ git_odb *db,+ git_transfer_progress_callback progress_cb,+ void *progress_payload);++/** * Determine the object-ID (sha1 hash) of a data buffer *- * The resulting SHA-1 OID will the itentifier for the data+ * The resulting SHA-1 OID will be the identifier for the data * buffer as if the data buffer it were to written to the ODB. *- * @param id the resulting object-ID.+ * @param out the resulting object-ID. * @param data data to hash * @param len size of the data * @param type of the data to hash * @return 0 or an error code */-GIT_EXTERN(int) git_odb_hash(git_oid *id, const void *data, size_t len, git_otype type);+GIT_EXTERN(int) git_odb_hash(git_oid *out, const void *data, size_t len, git_otype type); /** * Read a file from disk and fill a git_oid with the object id * that the file would have if it were written to the Object- * Database as an object of the given type. Similar functionality- * to git.git's `git hash-object` without the `-w` flag.+ * Database as an object of the given type (w/o applying filters).+ * Similar functionality to git.git's `git hash-object` without+ * the `-w` flag, however, with the --no-filters flag.+ * If you need filters, see git_repository_hashfile. * * @param out oid structure the result is written into. * @param path file to read and determine object id for
libgit2/include/git2/odb_backend.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,6 +10,7 @@ #include "common.h" #include "types.h" #include "oid.h"+#include "indexer.h" /** * @file git2/backend.h@@ -21,11 +22,24 @@ GIT_BEGIN_DECL struct git_odb_stream;+struct git_odb_writepack; -/** An instance for a custom backend */+/**+ * Function type for callbacks from git_odb_foreach.+ */+typedef int (*git_odb_foreach_cb)(const git_oid *id, void *payload);++/**+ * An instance for a custom backend+ */ struct git_odb_backend {+ unsigned int version; git_odb *odb; + /* read and read_prefix each return to libgit2 a buffer which+ * will be freed later. The buffer should be allocated using+ * the function git_odb_backend_malloc to ensure that it can+ * be safely freed later. */ int (* read)( void **, size_t *, git_otype *, struct git_odb_backend *,@@ -42,13 +56,17 @@ void **, size_t *, git_otype *, struct git_odb_backend *, const git_oid *,- unsigned int);+ size_t); int (* read_header)( size_t *, git_otype *, struct git_odb_backend *, const git_oid *); + /* The writer may assume that the object+ * has already been hashed and is passed+ * in the first parameter.+ */ int (* write)( git_oid *, struct git_odb_backend *,@@ -71,9 +89,25 @@ struct git_odb_backend *, const git_oid *); + int (* refresh)(struct git_odb_backend *);++ int (* foreach)(+ struct git_odb_backend *,+ git_odb_foreach_cb cb,+ void *payload);++ int (* writepack)(+ struct git_odb_writepack **,+ struct git_odb_backend *,+ git_transfer_progress_callback progress_cb,+ void *progress_payload);+ void (* free)(struct git_odb_backend *); }; +#define GIT_ODB_BACKEND_VERSION 1+#define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION}+ /** Streaming mode */ enum { GIT_STREAM_RDONLY = (1 << 1),@@ -84,7 +118,7 @@ /** A stream to read/write from a backend */ struct git_odb_stream { struct git_odb_backend *backend;- int mode;+ unsigned int mode; int (*read)(struct git_odb_stream *stream, char *buffer, size_t len); int (*write)(struct git_odb_stream *stream, const char *buffer, size_t len);@@ -92,8 +126,23 @@ void (*free)(struct git_odb_stream *stream); }; -GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir);-GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir, int compression_level, int do_fsync);+/** A stream to write a pack file to the ODB */+struct git_odb_writepack {+ struct git_odb_backend *backend;++ int (*add)(struct git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats);+ int (*commit)(struct git_odb_writepack *writepack, git_transfer_progress *stats);+ void (*free)(struct git_odb_writepack *writepack);+};++GIT_EXTERN(void *) git_odb_backend_malloc(git_odb_backend *backend, size_t len);++/**+ * Constructors for in-box ODB backends.+ */+GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **out, const char *objects_dir);+GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **out, const char *objects_dir, int compression_level, int do_fsync);+GIT_EXTERN(int) git_odb_backend_one_pack(git_odb_backend **out, const char *index_file); GIT_END_DECL
libgit2/include/git2/oid.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -30,11 +30,10 @@ #define GIT_OID_MINPREFIXLEN 4 /** Unique identity of any object (commit, tree, blob, tag). */-typedef struct _git_oid git_oid;-struct _git_oid {+typedef struct git_oid { /** raw binary formatted id */ unsigned char id[GIT_OID_RAWSZ];-};+} git_oid; /** * Parse a hex formatted object id into a git_oid.@@ -48,6 +47,16 @@ GIT_EXTERN(int) git_oid_fromstr(git_oid *out, const char *str); /**+ * Parse a hex formatted null-terminated string into a git_oid.+ *+ * @param out oid structure the result is written into.+ * @param str input hex string; must be at least 4 characters+ * long and null-terminated.+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_oid_fromstrp(git_oid *out, const char *str);++/** * Parse N characters of a hex formatted object id into a git_oid * * If N is odd, N-1 characters will be parsed instead.@@ -71,29 +80,29 @@ /** * Format a git_oid into a hex string. *- * @param str output hex string; must be pointing at the start of+ * @param out output hex string; must be pointing at the start of * the hex sequence and have at least the number of bytes * needed for an oid encoded in hex (40 bytes). Only the * oid digits are written; a '\\0' terminator must be added * by the caller if it is required. * @param oid oid structure to format. */-GIT_EXTERN(void) git_oid_fmt(char *str, const git_oid *oid);+GIT_EXTERN(void) git_oid_fmt(char *out, const git_oid *id); /** * Format a git_oid into a loose-object path string. * * The resulting string is "aa/...", where "aa" is the first two- * hex digitis of the oid and "..." is the remaining 38 digits.+ * hex digits of the oid and "..." is the remaining 38 digits. *- * @param str output hex string; must be pointing at the start of+ * @param out output hex string; must be pointing at the start of * the hex sequence and have at least the number of bytes * needed for an oid encoded in hex (41 bytes). Only the * oid digits are written; a '\\0' terminator must be added * by the caller if it is required.- * @param oid oid structure to format.+ * @param id oid structure to format. */-GIT_EXTERN(void) git_oid_pathfmt(char *str, const git_oid *oid);+GIT_EXTERN(void) git_oid_pathfmt(char *out, const git_oid *id); /** * Format a git_oid into a newly allocated c-string.@@ -102,7 +111,7 @@ * @return the c-string; NULL if memory is exhausted. Caller must * deallocate the string with git__free(). */-GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *oid);+GIT_EXTERN(char *) git_oid_allocfmt(const git_oid *id); /** * Format a git_oid into a buffer as a hex format c-string.@@ -115,11 +124,11 @@ * * @param out the buffer into which the oid string is output. * @param n the size of the out buffer.- * @param oid the oid structure to format.+ * @param id the oid structure to format. * @return the out buffer pointer, assuming no input parameter * errors, otherwise a pointer to an empty string. */-GIT_EXTERN(char *) git_oid_tostr(char *out, size_t n, const git_oid *oid);+GIT_EXTERN(char *) git_oid_tostr(char *out, size_t n, const git_oid *id); /** * Copy an oid from one structure to another.@@ -136,9 +145,33 @@ * @param b second oid structure. * @return <0, 0, >0 if a < b, a == b, a > b. */-GIT_EXTERN(int) git_oid_cmp(const git_oid *a, const git_oid *b);+GIT_INLINE(int) git_oid_cmp(const git_oid *a, const git_oid *b)+{+ const unsigned char *sha1 = a->id;+ const unsigned char *sha2 = b->id;+ int i; + for (i = 0; i < GIT_OID_RAWSZ; i++, sha1++, sha2++) {+ if (*sha1 != *sha2)+ return *sha1 - *sha2;+ }++ return 0;+}+ /**+ * Compare two oid structures for equality+ *+ * @param a first oid structure.+ * @param b second oid structure.+ * @return true if equal, false otherwise+ */+GIT_INLINE(int) git_oid_equal(const git_oid *a, const git_oid *b)+{+ return !git_oid_cmp(a, b);+}++/** * Compare the first 'len' hexadecimal characters (packets of 4 bits) * of two oid structures. *@@ -147,22 +180,24 @@ * @param len the number of hex chars to compare * @return 0 in case of a match */-GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, unsigned int len);+GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, size_t len); /** * Check if an oid equals an hex formatted object id. *- * @param a oid structure.+ * @param id oid structure. * @param str input hex string of an object id. * @return GIT_ENOTOID if str is not a valid hex string, * 0 in case of a match, GIT_ERROR otherwise. */-GIT_EXTERN(int) git_oid_streq(const git_oid *a, const char *str);+GIT_EXTERN(int) git_oid_streq(const git_oid *id, const char *str); /** * Check is an oid is all zeros.+ *+ * @return 1 if all zeros, 0 otherwise. */-GIT_EXTERN(int) git_oid_iszero(const git_oid *a);+GIT_EXTERN(int) git_oid_iszero(const git_oid *id); /** * OID Shortener object@@ -204,12 +239,12 @@ * GIT_ENOMEM error * * @param os a `git_oid_shorten` instance- * @param text_oid an OID in text form+ * @param text_id an OID in text form * @return the minimal length to uniquely identify all OIDs * added so far to the set; or an error code (<0) if an * error occurs. */-GIT_EXTERN(int) git_oid_shorten_add(git_oid_shorten *os, const char *text_oid);+GIT_EXTERN(int) git_oid_shorten_add(git_oid_shorten *os, const char *text_id); /** * Free an OID shortener instance
+ libgit2/include/git2/pack.h view
@@ -0,0 +1,143 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_pack_h__+#define INCLUDE_git_pack_h__++#include "common.h"+#include "oid.h"++/**+ * @file git2/pack.h+ * @brief Git pack management routines+ *+ * Packing objects+ * ---------------+ *+ * Creation of packfiles requires two steps:+ *+ * - First, insert all the objects you want to put into the packfile+ * using `git_packbuilder_insert` and `git_packbuilder_insert_tree`.+ * It's important to add the objects in recency order ("in the order+ * that they are 'reachable' from head").+ *+ * "ANY order will give you a working pack, ... [but it is] the thing+ * that gives packs good locality. It keeps the objects close to the+ * head (whether they are old or new, but they are _reachable_ from the+ * head) at the head of the pack. So packs actually have absolutely+ * _wonderful_ IO patterns." - Linus Torvalds+ * git.git/Documentation/technical/pack-heuristics.txt+ *+ * - Second, use `git_packbuilder_write` or `git_packbuilder_foreach` to+ * write the resulting packfile.+ *+ * libgit2 will take care of the delta ordering and generation.+ * `git_packbuilder_set_threads` can be used to adjust the number of+ * threads used for the process.+ *+ * See tests-clar/pack/packbuilder.c for an example.+ *+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Initialize a new packbuilder+ *+ * @param out The new packbuilder object+ * @param repo The repository+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_packbuilder_new(git_packbuilder **out, git_repository *repo);++/**+ * Set number of threads to spawn+ *+ * By default, libgit2 won't spawn any threads at all;+ * when set to 0, libgit2 will autodetect the number of+ * CPUs.+ *+ * @param pb The packbuilder+ * @param n Number of threads to spawn+ * @return number of actual threads to be used+ */+GIT_EXTERN(unsigned int) git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n);++/**+ * Insert a single object+ *+ * For an optimal pack it's mandatory to insert objects in recency order,+ * commits followed by trees and blobs.+ *+ * @param pb The packbuilder+ * @param id The oid of the commit+ * @param name The name; might be NULL+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_packbuilder_insert(git_packbuilder *pb, const git_oid *id, const char *name);++/**+ * Insert a root tree object+ *+ * This will add the tree as well as all referenced trees and blobs.+ *+ * @param pb The packbuilder+ * @param id The oid of the root tree+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *id);++/**+ * Write the new pack and the corresponding index to path+ *+ * @param pb The packbuilder+ * @param path Directory to store the new pack and index+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_packbuilder_write(git_packbuilder *pb, const char *file);++typedef int (*git_packbuilder_foreach_cb)(void *buf, size_t size, void *payload);+/**+ * Create the new pack and pass each object to the callback+ *+ * @param pb the packbuilder+ * @param cb the callback to call with each packed object's buffer+ * @param payload the callback's data+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, git_packbuilder_foreach_cb cb, void *payload);++/**+ * Get the total number of objects the packbuilder will write out+ *+ * @param pb the packbuilder+ * @return+ */+GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb);++/**+ * Get the number of objects the packbuilder has already written out+ *+ * @param pb the packbuilder+ * @return+ */+GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb);++/**+ * Free the packbuilder and all associated data+ *+ * @param pb The packbuilder+ */+GIT_EXTERN(void) git_packbuilder_free(git_packbuilder *pb);++/** @} */+GIT_END_DECL+#endif
+ libgit2/include/git2/push.h view
@@ -0,0 +1,132 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_push_h__+#define INCLUDE_git_push_h__++#include "common.h"+#include "types.h"++/**+ * @file git2/push.h+ * @brief Git push management functions+ * @defgroup git_push push management functions+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Controls the behavior of a git_push object.+ */+typedef struct {+ unsigned int version;++ /**+ * If the transport being used to push to the remote requires the creation+ * of a pack file, this controls the number of worker threads used by+ * the packbuilder when creating that pack file to be sent to the remote.+ *+ * If set to 0, the packbuilder will auto-detect the number of threads+ * to create. The default value is 1.+ */+ unsigned int pb_parallelism;+} git_push_options;++#define GIT_PUSH_OPTIONS_VERSION 1+#define GIT_PUSH_OPTIONS_INIT { GIT_PUSH_OPTIONS_VERSION }++/**+ * Create a new push object+ *+ * @param out New push object+ * @param remote Remote instance+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_push_new(git_push **out, git_remote *remote);++/**+ * Set options on a push object+ *+ * @param push The push object+ * @param opts The options to set on the push object+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_push_set_options(+ git_push *push,+ const git_push_options *opts);++/**+ * Add a refspec to be pushed+ *+ * @param push The push object+ * @param refspec Refspec string+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_push_add_refspec(git_push *push, const char *refspec);++/**+ * Update remote tips after a push+ *+ * @param push The push object+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_push_update_tips(git_push *push);++/**+ * Actually push all given refspecs+ *+ * Note: To check if the push was successful (i.e. all remote references+ * have been updated as requested), you need to call both+ * `git_push_unpack_ok` and `git_push_status_foreach`. The remote+ * repository might have refused to update some or all of the references.+ *+ * @param push The push object+ *+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_push_finish(git_push *push);++/**+ * Check if remote side successfully unpacked+ *+ * @param push The push object+ *+ * @return true if equal, false otherwise+ */+GIT_EXTERN(int) git_push_unpack_ok(git_push *push);++/**+ * Call callback `cb' on each status+ *+ * For each of the updated references, we receive a status report in the+ * form of `ok refs/heads/master` or `ng refs/heads/master <msg>`.+ * `msg != NULL` means the reference has not been updated for the given+ * reason.+ *+ * @param push The push object+ * @param cb The callback to call on each object+ *+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code+ */+GIT_EXTERN(int) git_push_status_foreach(git_push *push,+ int (*cb)(const char *ref, const char *msg, void *data),+ void *data);++/**+ * Free the given push object+ *+ * @param push The push object+ */+GIT_EXTERN(void) git_push_free(git_push *push);++/** @} */+GIT_END_DECL+#endif
+ libgit2/include/git2/refdb.h view
@@ -0,0 +1,98 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_refdb_h__+#define INCLUDE_git_refdb_h__++#include "common.h"+#include "types.h"+#include "oid.h"+#include "refs.h"++/**+ * @file git2/refdb.h+ * @brief Git custom refs backend functions+ * @defgroup git_refdb Git custom refs backend API+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Create a new reference. Either an oid or a symbolic target must be+ * specified.+ *+ * @param refdb the reference database to associate with this reference+ * @param name the reference name+ * @param oid the object id for a direct reference+ * @param symbolic the target for a symbolic reference+ * @return the created git_reference or NULL on error+ */+GIT_EXTERN(git_reference *) git_reference__alloc(+ git_refdb *refdb,+ const char *name,+ const git_oid *oid,+ const char *symbolic);++/**+ * Create a new reference database with no backends.+ *+ * Before the Ref DB can be used for read/writing, a custom database+ * backend must be manually set using `git_refdb_set_backend()`+ *+ * @param out location to store the database pointer, if opened.+ * Set to NULL if the open failed.+ * @param repo the repository+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_refdb_new(git_refdb **out, git_repository *repo);++/**+ * Create a new reference database and automatically add+ * the default backends:+ *+ * - git_refdb_dir: read and write loose and packed refs+ * from disk, assuming the repository dir as the folder+ *+ * @param out location to store the database pointer, if opened.+ * Set to NULL if the open failed.+ * @param repo the repository+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_refdb_open(git_refdb **out, git_repository *repo);++/**+ * Suggests that the given refdb compress or optimize its references.+ * This mechanism is implementation specific. For on-disk reference+ * databases, for example, this may pack all loose references.+ */+GIT_EXTERN(int) git_refdb_compress(git_refdb *refdb);++/**+ * Close an open reference database.+ *+ * @param refdb reference database pointer or NULL+ */+GIT_EXTERN(void) git_refdb_free(git_refdb *refdb);++/**+ * Sets the custom backend to an existing reference DB+ *+ * Read <refdb_backends.h> for more information.+ *+ * @param refdb database to add the backend to+ * @param backend pointer to a git_refdb_backend instance+ * @param priority Value for ordering the backends queue+ * @return 0 on success; error code otherwise+ */+GIT_EXTERN(int) git_refdb_set_backend(+ git_refdb *refdb,+ git_refdb_backend *backend);++/** @} */+GIT_END_DECL++#endif
+ libgit2/include/git2/refdb_backend.h view
@@ -0,0 +1,110 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_refdb_backend_h__+#define INCLUDE_git_refdb_backend_h__++#include "common.h"+#include "types.h"+#include "refs.h"+#include "oid.h"++/**+ * @file git2/refdb_backend.h+ * @brief Git custom refs backend functions+ * @defgroup git_refdb_backend Git custom refs backend API+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/** An instance for a custom backend */+struct git_refdb_backend {+ unsigned int version;++ /**+ * Queries the refdb backend to determine if the given ref_name+ * exists. A refdb implementation must provide this function.+ */+ int (*exists)(+ int *exists,+ struct git_refdb_backend *backend,+ const char *ref_name);++ /**+ * Queries the refdb backend for a given reference. A refdb+ * implementation must provide this function.+ */+ int (*lookup)(+ git_reference **out,+ struct git_refdb_backend *backend,+ const char *ref_name);++ /**+ * Enumerates each reference in the refdb. A refdb implementation must+ * provide this function.+ */+ int (*foreach)(+ struct git_refdb_backend *backend,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload);++ /**+ * Enumerates each reference in the refdb that matches the given+ * glob string. A refdb implementation may provide this function;+ * if it is not provided, foreach will be used and the results filtered+ * against the glob.+ */+ int (*foreach_glob)(+ struct git_refdb_backend *backend,+ const char *glob,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload);++ /**+ * Writes the given reference to the refdb. A refdb implementation+ * must provide this function.+ */+ int (*write)(struct git_refdb_backend *backend, const git_reference *ref);++ /**+ * Deletes the given reference from the refdb. A refdb implementation+ * must provide this function.+ */+ int (*delete)(struct git_refdb_backend *backend, const git_reference *ref);++ /**+ * Suggests that the given refdb compress or optimize its references.+ * This mechanism is implementation specific. (For on-disk reference+ * databases, this may pack all loose references.) A refdb+ * implementation may provide this function; if it is not provided,+ * nothing will be done.+ */+ int (*compress)(struct git_refdb_backend *backend);++ /**+ * Frees any resources held by the refdb. A refdb implementation may+ * provide this function; if it is not provided, nothing will be done.+ */+ void (*free)(struct git_refdb_backend *backend);+};++#define GIT_ODB_BACKEND_VERSION 1+#define GIT_ODB_BACKEND_INIT {GIT_ODB_BACKEND_VERSION}++/**+ * Constructors for default refdb backends.+ */+GIT_EXTERN(int) git_refdb_backend_fs(+ struct git_refdb_backend **backend_out,+ git_repository *repo,+ git_refdb *refdb);++GIT_END_DECL++#endif
libgit2/include/git2/reflog.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -23,41 +23,54 @@ /** * Read the reflog for the given reference *+ * If there is no reflog file for the given+ * reference yet, an empty reflog object will+ * be returned.+ * * The reflog must be freed manually by using * git_reflog_free(). *- * @param reflog pointer to reflog+ * @param out pointer to reflog * @param ref reference to read the reflog for * @return 0 or an error code */-GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref);+GIT_EXTERN(int) git_reflog_read(git_reflog **out, const git_reference *ref); /**- * Write a new reflog for the given reference- *- * If there is no reflog file for the given- * reference yet, it will be created.+ * Write an existing in-memory reflog object back to disk+ * using an atomic file lock. *- * `oid_old` may be NULL in case it's a new reference.+ * @param reflog an existing reflog object+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_reflog_write(git_reflog *reflog);++/**+ * Add a new entry to the reflog. * * `msg` is optional and can be NULL. *- * @param ref the changed reference- * @param oid_old the OID the reference was pointing to+ * @param reflog an existing reflog object+ * @param id the OID the reference is now pointing to * @param committer the signature of the committer * @param msg the reflog message * @return 0 or an error code */-GIT_EXTERN(int) git_reflog_write(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg);+GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *id, const git_signature *committer, const char *msg); /** * Rename the reflog for the given reference *+ * The reflog to be renamed is expected to already exist+ *+ * The new name will be checked for validity.+ * See `git_reference_create_symbolic()` for rules about valid names.+ * * @param ref the reference- * @param new_name the new name of the reference- * @return 0 or an error code+ * @param name the new name of the reference+ * @return 0 on success, GIT_EINVALIDSPEC or an error code */-GIT_EXTERN(int) git_reflog_rename(git_reference *ref, const char *new_name);+GIT_EXTERN(int) git_reflog_rename(git_reference *ref, const char *name); /** * Delete the reflog for the given reference@@ -73,24 +86,50 @@ * @param reflog the previously loaded reflog * @return the number of log entries */-GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog);+GIT_EXTERN(size_t) git_reflog_entrycount(git_reflog *reflog); /** * Lookup an entry by its index *+ * Requesting the reflog entry with an index of 0 (zero) will+ * return the most recently created entry.+ * * @param reflog a previously loaded reflog- * @param idx the position to lookup+ * @param idx the position of the entry to lookup. Should be greater than or+ * equal to 0 (zero) and less than `git_reflog_entrycount()`. * @return the entry; NULL if not found */-GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx);+GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, size_t idx); /**+ * Remove an entry from the reflog by its index+ *+ * To ensure there's no gap in the log history, set `rewrite_previous_entry`+ * param value to 1. When deleting entry `n`, member old_oid of entry `n-1`+ * (if any) will be updated with the value of member new_oid of entry `n+1`.+ *+ * @param reflog a previously loaded reflog.+ *+ * @param idx the position of the entry to remove. Should be greater than or+ * equal to 0 (zero) and less than `git_reflog_entrycount()`.+ *+ * @param rewrite_previous_entry 1 to rewrite the history; 0 otherwise.+ *+ * @return 0 on success, GIT_ENOTFOUND if the entry doesn't exist+ * or an error code.+ */+GIT_EXTERN(int) git_reflog_drop(+ git_reflog *reflog,+ size_t idx,+ int rewrite_previous_entry);++/** * Get the old oid * * @param entry a reflog entry * @return the old oid */-GIT_EXTERN(const git_oid *) git_reflog_entry_oidold(const git_reflog_entry *entry);+GIT_EXTERN(const git_oid *) git_reflog_entry_id_old(const git_reflog_entry *entry); /** * Get the new oid@@ -98,7 +137,7 @@ * @param entry a reflog entry * @return the new oid at this time */-GIT_EXTERN(const git_oid *) git_reflog_entry_oidnew(const git_reflog_entry *entry);+GIT_EXTERN(const git_oid *) git_reflog_entry_id_new(const git_reflog_entry *entry); /** * Get the committer of this entry@@ -106,15 +145,15 @@ * @param entry a reflog entry * @return the committer */-GIT_EXTERN(git_signature *) git_reflog_entry_committer(const git_reflog_entry *entry);+GIT_EXTERN(const git_signature *) git_reflog_entry_committer(const git_reflog_entry *entry); /**- * Get the log msg+ * Get the log message * * @param entry a reflog entry * @return the log msg */-GIT_EXTERN(char *) git_reflog_entry_msg(const git_reflog_entry *entry);+GIT_EXTERN(const char *) git_reflog_entry_message(const git_reflog_entry *entry); /** * Free the reflog
libgit2/include/git2/refs.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,6 +10,7 @@ #include "common.h" #include "types.h" #include "oid.h"+#include "strarray.h" /** * @file git2/refs.h@@ -21,176 +22,222 @@ GIT_BEGIN_DECL /**- * Lookup a reference by its name in a repository.+ * Lookup a reference by name in a repository. *- * The generated reference must be freed by the user.+ * The returned reference must be freed by the user. *- * @param reference_out pointer to the looked-up reference+ * The name will be checked for validity.+ * See `git_reference_create_symbolic()` for rules about valid names.+ *+ * @param out pointer to the looked-up reference * @param repo the repository to look up the reference- * @param name the long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...)- * @return 0 or an error code+ * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)+ * @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code. */-GIT_EXTERN(int) git_reference_lookup(git_reference **reference_out, git_repository *repo, const char *name);+GIT_EXTERN(int) git_reference_lookup(git_reference **out, git_repository *repo, const char *name); /** * Lookup a reference by name and resolve immediately to OID. *- * @param oid Pointer to oid to be filled in+ * This function provides a quick way to resolve a reference name straight+ * through to the object id that it refers to. This avoids having to+ * allocate or free any `git_reference` objects for simple situations.+ *+ * The name will be checked for validity.+ * See `git_reference_symbolic_create()` for rules about valid names.+ *+ * @param out Pointer to oid to be filled in * @param repo The repository in which to look up the reference * @param name The long name for the reference- * @return 0 on success, -1 if name could not be resolved+ * @return 0 on success, ENOTFOUND, EINVALIDSPEC or an error code. */-GIT_EXTERN(int) git_reference_name_to_oid(+GIT_EXTERN(int) git_reference_name_to_id( git_oid *out, git_repository *repo, const char *name); /** * Create a new symbolic reference. *- * The reference will be created in the repository and written- * to the disk.+ * A symbolic reference is a reference name that refers to another+ * reference name. If the other name moves, the symbolic name will move,+ * too. As a simple example, the "HEAD" reference might refer to+ * "refs/heads/master" while on the "master" branch of a repository. *- * The generated reference must be freed by the user.+ * The symbolic reference will be created in the repository and written to+ * the disk. The generated reference object must be freed by the user. *- * If `force` is true and there already exists a reference- * with the same name, it will be overwritten.+ * Valid reference names must follow one of two patterns: *- * @param ref_out Pointer to the newly created reference+ * 1. Top-level names must contain only capital letters and underscores,+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the+ * sequences ".." and "@{" which have special meaning to revparse.+ *+ * This function will return an error if a reference already exists with the+ * given name unless `force` is true, in which case it will be overwritten.+ *+ * @param out Pointer to the newly created reference * @param repo Repository where that reference will live * @param name The name of the reference * @param target The target of the reference * @param force Overwrite existing references- * @return 0 or an error code+ * @return 0 on success, EEXISTS, EINVALIDSPEC or an error code */-GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force);+GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repository *repo, const char *name, const char *target, int force); /**- * Create a new object id reference.+ * Create a new direct reference. *- * The reference will be created in the repository and written- * to the disk.+ * A direct reference (also called an object id reference) refers directly+ * to a specific object id (a.k.a. OID or SHA) in the repository. The id+ * permanently refers to the object (although the reference itself can be+ * moved). For example, in libgit2 the direct ref "refs/tags/v0.17.0"+ * refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977. *- * The generated reference must be freed by the user.+ * The direct reference will be created in the repository and written to+ * the disk. The generated reference object must be freed by the user. *- * If `force` is true and there already exists a reference- * with the same name, it will be overwritten.+ * Valid reference names must follow one of two patterns: *- * @param ref_out Pointer to the newly created reference+ * 1. Top-level names must contain only capital letters and underscores,+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the+ * sequences ".." and "@{" which have special meaning to revparse.+ *+ * This function will return an error if a reference already exists with the+ * given name unless `force` is true, in which case it will be overwritten.+ *+ * @param out Pointer to the newly created reference * @param repo Repository where that reference will live * @param name The name of the reference * @param id The object id pointed to by the reference. * @param force Overwrite existing references- * @return 0 or an error code+ * @return 0 on success, EEXISTS, EINVALIDSPEC or an error code */-GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force);+GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, const char *name, const git_oid *id, int force); /**- * Get the OID pointed to by a reference.+ * Get the OID pointed to by a direct reference. *- * Only available if the reference is direct (i.e. not symbolic)+ * Only available if the reference is direct (i.e. an object id reference,+ * not a symbolic one). *+ * To find the OID of a symbolic ref, call `git_reference_resolve()` and+ * then this function (or maybe use `git_reference_name_to_id()` to+ * directly resolve a reference name all the way through to an OID).+ * * @param ref The reference * @return a pointer to the oid if available, NULL otherwise */-GIT_EXTERN(const git_oid *) git_reference_oid(git_reference *ref);+GIT_EXTERN(const git_oid *) git_reference_target(const git_reference *ref); /**- * Get full name to the reference pointed by this reference+ * Get full name to the reference pointed to by a symbolic reference. *- * Only available if the reference is symbolic+ * Only available if the reference is symbolic. * * @param ref The reference * @return a pointer to the name if available, NULL otherwise */-GIT_EXTERN(const char *) git_reference_target(git_reference *ref);+GIT_EXTERN(const char *) git_reference_symbolic_target(const git_reference *ref); /**- * Get the type of a reference+ * Get the type of a reference. * * Either direct (GIT_REF_OID) or symbolic (GIT_REF_SYMBOLIC) * * @param ref The reference * @return the type */-GIT_EXTERN(git_ref_t) git_reference_type(git_reference *ref);+GIT_EXTERN(git_ref_t) git_reference_type(const git_reference *ref); /**- * Get the full name of a reference+ * Get the full name of a reference. *+ * See `git_reference_create_symbolic()` for rules about valid names.+ * * @param ref The reference * @return the full name for the ref */-GIT_EXTERN(const char *) git_reference_name(git_reference *ref);+GIT_EXTERN(const char *) git_reference_name(const git_reference *ref); /**- * Resolve a symbolic reference+ * Resolve a symbolic reference to a direct reference. *- * Thie method iteratively peels a symbolic reference- * until it resolves to a direct reference to an OID.+ * This method iteratively peels a symbolic reference until it resolves to+ * a direct reference to an OID. *- * The peeled reference is returned in the `resolved_ref`- * argument, and must be freed manually once it's no longer- * needed.+ * The peeled reference is returned in the `resolved_ref` argument, and+ * must be freed manually once it's no longer needed. *- * If a direct reference is passed as an argument,- * a copy of that reference is returned. This copy must- * be manually freed too.+ * If a direct reference is passed as an argument, a copy of that+ * reference is returned. This copy must be manually freed too. * * @param resolved_ref Pointer to the peeled reference * @param ref The reference * @return 0 or an error code */-GIT_EXTERN(int) git_reference_resolve(git_reference **resolved_ref, git_reference *ref);+GIT_EXTERN(int) git_reference_resolve(git_reference **out, const git_reference *ref); /**- * Get the repository where a reference resides+ * Get the repository where a reference resides. * * @param ref The reference * @return a pointer to the repo */-GIT_EXTERN(git_repository *) git_reference_owner(git_reference *ref);+GIT_EXTERN(git_repository *) git_reference_owner(const git_reference *ref); /**- * Set the symbolic target of a reference.+ * Create a new reference with the same name as the given reference but a+ * different symbolic target. The reference must be a symbolic reference,+ * otherwise this will fail. *- * The reference must be a symbolic reference, otherwise- * this method will fail.+ * The new reference will be written to disk, overwriting the given reference. *- * The reference will be automatically updated in- * memory and on disk.+ * The target name will be checked for validity.+ * See `git_reference_create_symbolic()` for rules about valid names. *+ * @param out Pointer to the newly created reference * @param ref The reference * @param target The new target for the reference- * @return 0 or an error code+ * @return 0 on success, EINVALIDSPEC or an error code */-GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const char *target);+GIT_EXTERN(int) git_reference_symbolic_set_target(+ git_reference **out,+ git_reference *ref,+ const char *target); /**- * Set the OID target of a reference.- *- * The reference must be a direct reference, otherwise- * this method will fail.+ * Create a new reference with the same name as the given reference but a+ * different OID target. The reference must be a direct reference, otherwise+ * this will fail. *- * The reference will be automatically updated in- * memory and on disk.+ * The new reference will be written to disk, overwriting the given reference. *+ * @param out Pointer to the newly created reference * @param ref The reference * @param id The new target OID for the reference * @return 0 or an error code */-GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id);+GIT_EXTERN(int) git_reference_set_target(+ git_reference **out,+ git_reference *ref,+ const git_oid *id); /**- * Rename an existing reference+ * Rename an existing reference. * * This method works for both direct and symbolic references.- * The new name will be checked for validity and may be- * modified into a normalized form. *- * The given git_reference will be updated in place.+ * The new name will be checked for validity.+ * See `git_reference_create_symbolic()` for rules about valid names. *- * The reference will be immediately renamed in-memory- * and on disk.+ * On success, the given git_reference will be deleted from disk and a+ * new `git_reference` will be returned. *+ * The reference will be immediately renamed in-memory and on disk.+ * * If the `force` flag is not enabled, and there's already * a reference with the given name, the renaming will fail. *@@ -200,20 +247,23 @@ * the reflog if it exists. * * @param ref The reference to rename- * @param new_name The new name for the reference+ * @param name The new name for the reference * @param force Overwrite an existing reference- * @return 0 or an error code+ * @return 0 on success, EINVALIDSPEC, EEXISTS or an error code * */-GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, int force);+GIT_EXTERN(int) git_reference_rename(+ git_reference **out,+ git_reference *ref,+ const char *new_name,+ int force); /**- * Delete an existing reference- *- * This method works for both direct and symbolic references.+ * Delete an existing reference. *- * The reference will be immediately removed on disk and from- * memory. The given reference pointer will no longer be valid.+ * This method works for both direct and symbolic references. The reference+ * will be immediately removed on disk but the memory will not be freed.+ * Callers must call `git_reference_free`. * * @param ref The reference to remove * @return 0 or an error code@@ -221,108 +271,209 @@ GIT_EXTERN(int) git_reference_delete(git_reference *ref); /**- * Pack all the loose references in the repository+ * Fill a list with all the references that can be found in a repository. *- * This method will load into the cache all the loose- * references on the repository and update the- * `packed-refs` file with them.+ * Using the `list_flags` parameter, the listed references may be filtered+ * by type (`GIT_REF_OID` or `GIT_REF_SYMBOLIC`) or using a bitwise OR of+ * `git_ref_t` values. To include packed refs, include `GIT_REF_PACKED`.+ * For convenience, use the value `GIT_REF_LISTALL` to obtain all+ * references, including packed ones. *- * Once the `packed-refs` file has been written properly,- * the loose references will be removed from disk.+ * The string array will be filled with the names of all references; these+ * values are owned by the user and should be free'd manually when no+ * longer needed, using `git_strarray_free()`. *- * @param repo Repository where the loose refs will be packed+ * @param array Pointer to a git_strarray structure where+ * the reference names will be stored+ * @param repo Repository where to find the refs+ * @param list_flags Filtering flags for the reference listing * @return 0 or an error code */-GIT_EXTERN(int) git_reference_packall(git_repository *repo);+GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo, unsigned int list_flags); +typedef int (*git_reference_foreach_cb)(const char *refname, void *payload);+ /**- * Fill a list with all the references that can be found- * in a repository.+ * Perform a callback on each reference in the repository. *- * The listed references may be filtered by type, or using- * a bitwise OR of several types. Use the magic value- * `GIT_REF_LISTALL` to obtain all references, including- * packed ones.+ * Using the `list_flags` parameter, the references may be filtered by+ * type (`GIT_REF_OID` or `GIT_REF_SYMBOLIC`) or using a bitwise OR of+ * `git_ref_t` values. To include packed refs, include `GIT_REF_PACKED`.+ * For convenience, use the value `GIT_REF_LISTALL` to obtain all+ * references, including packed ones. *- * The string array will be filled with the names of all- * references; these values are owned by the user and- * should be free'd manually when no longer needed, using- * `git_strarray_free`.+ * The `callback` function will be called for each reference in the+ * repository, receiving the name of the reference and the `payload` value+ * passed to this method. Returning a non-zero value from the callback+ * will terminate the iteration. *- * @param array Pointer to a git_strarray structure where- * the reference names will be stored * @param repo Repository where to find the refs- * @param list_flags Filtering flags for the reference- * listing.- * @return 0 or an error code+ * @param list_flags Filtering flags for the reference listing.+ * @param callback Function which will be called for every listed ref+ * @param payload Additional data to pass to the callback+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */-GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo, unsigned int list_flags);+GIT_EXTERN(int) git_reference_foreach(+ git_repository *repo,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload); +/**+ * Free the given reference.+ *+ * @param ref git_reference+ */+GIT_EXTERN(void) git_reference_free(git_reference *ref); /**- * Perform an operation on each reference in the repository+ * Compare two references. *- * The processed references may be filtered by type, or using- * a bitwise OR of several types. Use the magic value- * `GIT_REF_LISTALL` to obtain all references, including- * packed ones.+ * @param ref1 The first git_reference+ * @param ref2 The second git_reference+ * @return 0 if the same, else a stable but meaningless ordering.+ */+GIT_EXTERN(int) git_reference_cmp(git_reference *ref1, git_reference *ref2);++/**+ * Perform a callback on each reference in the repository whose name+ * matches the given pattern. *- * The `callback` function will be called for each of the references- * in the repository, and will receive the name of the reference and- * the `payload` value passed to this method.+ * This function acts like `git_reference_foreach()` with an additional+ * pattern match being applied to the reference name before issuing the+ * callback function. See that function for more information. *+ * The pattern is matched using fnmatch or "glob" style where a '*' matches+ * any sequence of letters, a '?' matches any letter, and square brackets+ * can be used to define character ranges (such as "[0-9]" for digits).+ * * @param repo Repository where to find the refs- * @param list_flags Filtering flags for the reference- * listing.+ * @param glob Pattern to match (fnmatch-style) against reference name.+ * @param list_flags Filtering flags for the reference listing. * @param callback Function which will be called for every listed ref * @param payload Additional data to pass to the callback- * @return 0 or an error code+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */-GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload);+GIT_EXTERN(int) git_reference_foreach_glob(+ git_repository *repo,+ const char *glob,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload); /**- * Check if a reference has been loaded from a packfile+ * Check if a reflog exists for the specified reference. * * @param ref A git reference- * @return 0 in case it's not packed; 1 otherwise+ *+ * @return 0 when no reflog can be found, 1 when it exists;+ * otherwise an error code. */-GIT_EXTERN(int) git_reference_is_packed(git_reference *ref);+GIT_EXTERN(int) git_reference_has_log(git_reference *ref); /**- * Reload a reference from disk+ * Check if a reference is a local branch. *- * Reference pointers may become outdated if the Git- * repository is accessed simultaneously by other clients- * whilt the library is open.+ * @param ref A git reference *- * This method forces a reload of the reference from disk,- * to ensure that the provided information is still- * reliable.+ * @return 1 when the reference lives in the refs/heads+ * namespace; 0 otherwise.+ */+GIT_EXTERN(int) git_reference_is_branch(git_reference *ref);++/**+ * Check if a reference is a remote tracking branch *- * If the reload fails (e.g. the reference no longer exists- * on disk, or has become corrupted), an error code will be- * returned and the reference pointer will be invalidated.+ * @param ref A git reference *- * @param ref The reference to reload- * @return 0 on success, or an error code+ * @return 1 when the reference lives in the refs/remotes+ * namespace; 0 otherwise. */-GIT_EXTERN(int) git_reference_reload(git_reference *ref);+GIT_EXTERN(int) git_reference_is_remote(git_reference *ref); ++typedef enum {+ GIT_REF_FORMAT_NORMAL = 0,++ /**+ * Control whether one-level refnames are accepted+ * (i.e., refnames that do not contain multiple /-separated+ * components). Those are expected to be written only using+ * uppercase letters and underscore (FETCH_HEAD, ...)+ */+ GIT_REF_FORMAT_ALLOW_ONELEVEL = (1 << 0),++ /**+ * Interpret the provided name as a reference pattern for a+ * refspec (as used with remote repositories). If this option+ * is enabled, the name is allowed to contain a single * (<star>)+ * in place of a one full pathname component+ * (e.g., foo/<star>/bar but not foo/bar<star>).+ */+ GIT_REF_FORMAT_REFSPEC_PATTERN = (1 << 1),+} git_reference_normalize_t;+ /**- * Free the given reference+ * Normalize reference name and check validity. *- * @param ref git_reference+ * This will normalize the reference name by removing any leading slash+ * '/' characters and collapsing runs of adjacent slashes between name+ * components into a single slash.+ *+ * Once normalized, if the reference name is valid, it will be returned in+ * the user allocated buffer.+ *+ * See `git_reference_create_symbolic()` for rules about valid names.+ *+ * @param buffer_out User allocated buffer to store normalized name+ * @param buffer_size Size of buffer_out+ * @param name Reference name to be checked.+ * @param flags Flags to constrain name validation rules - see the+ * GIT_REF_FORMAT constants above.+ * @return 0 on success, GIT_EBUFS if buffer is too small, EINVALIDSPEC+ * or an error code. */-GIT_EXTERN(void) git_reference_free(git_reference *ref);+GIT_EXTERN(int) git_reference_normalize_name(+ char *buffer_out,+ size_t buffer_size,+ const char *name,+ unsigned int flags); /**- * Compare two references.+ * Recursively peel reference until object of the specified type is found. *- * @param ref1 The first git_reference- * @param ref2 The second git_reference- * @return 0 if the same, else a stable but meaningless ordering.+ * The retrieved `peeled` object is owned by the repository+ * and should be closed with the `git_object_free` method.+ *+ * If you pass `GIT_OBJ_ANY` as the target type, then the object+ * will be peeled until a non-tag object is met.+ *+ * @param peeled Pointer to the peeled git_object+ * @param ref The reference to be processed+ * @param target_type The type of the requested object (GIT_OBJ_COMMIT,+ * GIT_OBJ_TAG, GIT_OBJ_TREE, GIT_OBJ_BLOB or GIT_OBJ_ANY).+ * @return 0 on success, GIT_EAMBIGUOUS, GIT_ENOTFOUND or an error code */-GIT_EXTERN(int) git_reference_cmp(git_reference *ref1, git_reference *ref2);+GIT_EXTERN(int) git_reference_peel(+ git_object **out,+ git_reference *ref,+ git_otype type);++/**+ * Ensure the reference name is well-formed.+ *+ * Valid reference names must follow one of two patterns:+ *+ * 1. Top-level names must contain only capital letters and underscores,+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the+ * sequences ".." and "@{" which have special meaning to revparse.+ *+ * @param refname name to be checked.+ * @return 1 if the reference name is acceptable; 0 if it isn't+ */+GIT_EXTERN(int) git_reference_is_valid_name(const char *refname); /** @} */ GIT_END_DECL
libgit2/include/git2/refspec.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -36,6 +36,14 @@ GIT_EXTERN(const char *) git_refspec_dst(const git_refspec *refspec); /**+ * Get the force update setting+ *+ * @param refspec the refspec+ * @return 1 if force update has been set, 0 otherwise+ */+GIT_EXTERN(int) git_refspec_force(const git_refspec *refspec);++/** * Check if a refspec's source descriptor matches a reference * * @param refspec the refspec@@ -45,15 +53,35 @@ GIT_EXTERN(int) git_refspec_src_matches(const git_refspec *refspec, const char *refname); /**+ * Check if a refspec's destination descriptor matches a reference+ *+ * @param refspec the refspec+ * @param refname the name of the reference to check+ * @return 1 if the refspec matches, 0 otherwise+ */+GIT_EXTERN(int) git_refspec_dst_matches(const git_refspec *refspec, const char *refname);++/** * Transform a reference to its target following the refspec's rules * * @param out where to store the target name- * @param outlen the size ouf the `out` buffer+ * @param outlen the size of the `out` buffer * @param spec the refspec * @param name the name of the reference to transform * @return 0, GIT_EBUFS or another error */ GIT_EXTERN(int) git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name);++/**+ * Transform a target reference to its source reference following the refspec's rules+ *+ * @param out where to store the source reference name+ * @param outlen the size of the `out` buffer+ * @param spec the refspec+ * @param name the name of the reference to transform+ * @return 0, GIT_EBUFS or another error+ */+GIT_EXTERN(int) git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name); GIT_END_DECL
libgit2/include/git2/remote.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -12,6 +12,8 @@ #include "refspec.h" #include "net.h" #include "indexer.h"+#include "strarray.h"+#include "transport.h" /** * @file git2/remote.h@@ -22,6 +24,7 @@ */ GIT_BEGIN_DECL +typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); /* * TODO: This functions still need to be implemented: * - _listcb/_foreach@@ -31,35 +34,64 @@ */ /**+ * Add a remote with the default fetch refspec to the repository's configuration. This+ * calls git_remote_save before returning.+ *+ * @param out the resulting remote+ * @param repo the repository in which to create the remote+ * @param name the remote's name+ * @param url the remote's url+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code+ */+GIT_EXTERN(int) git_remote_create(+ git_remote **out,+ git_repository *repo,+ const char *name,+ const char *url);++/** * Create a remote in memory *- * Create a remote with the default refspecs in memory. You can use- * this when you have a URL instead of a remote's name.+ * Create a remote with the given refspec in memory. You can use+ * this when you have a URL instead of a remote's name. Note that in-memory+ * remotes cannot be converted to persisted remotes. *+ * The name, when provided, will be checked for validity.+ * See `git_tag_create()` for rules about valid names.+ * * @param out pointer to the new remote object- * @param repo the associtated repository- * @param name the remote's name+ * @param repo the associated repository+ * @param fetch the fetch refspec to use for this remote. May be NULL for defaults. * @param url the remote repository's URL- * @param fetch the fetch refspec to use for this remote * @return 0 or an error code */-GIT_EXTERN(int) git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch);+GIT_EXTERN(int) git_remote_create_inmemory(+ git_remote **out,+ git_repository *repo,+ const char *fetch,+ const char *url); /** * Get the information for a particular remote *+ * The name will be checked for validity.+ * See `git_tag_create()` for rules about valid names.+ * * @param out pointer to the new remote object- * @param cfg the repository's configuration+ * @param repo the associated repository * @param name the remote's name- * @return 0 or an error code+ * @return 0, GIT_ENOTFOUND, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_remote_load(git_remote **out, git_repository *repo, const char *name); /** * Save a remote to its repository's configuration *+ * One can't save a in-memory remote. Doing so will+ * result in a GIT_EINVALIDSPEC being returned.+ * * @param remote the remote to save to config- * @return 0 or an error code+ * @return 0, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_remote_save(const git_remote *remote); @@ -67,9 +99,9 @@ * Get the remote's name * * @param remote the remote- * @return a pointer to the name+ * @return a pointer to the name or NULL for in-memory remotes */-GIT_EXTERN(const char *) git_remote_name(git_remote *remote);+GIT_EXTERN(const char *) git_remote_name(const git_remote *remote); /** * Get the remote's url@@ -77,9 +109,39 @@ * @param remote the remote * @return a pointer to the url */-GIT_EXTERN(const char *) git_remote_url(git_remote *remote);+GIT_EXTERN(const char *) git_remote_url(const git_remote *remote); /**+ * Get the remote's url for pushing+ *+ * @param remote the remote+ * @return a pointer to the url or NULL if no special url for pushing is set+ */+GIT_EXTERN(const char *) git_remote_pushurl(const git_remote *remote);++/**+ * Set the remote's url+ *+ * Existing connections will not be updated.+ *+ * @param remote the remote+ * @param url the url to set+ * @return 0 or an error value+ */+GIT_EXTERN(int) git_remote_set_url(git_remote *remote, const char* url);++/**+ * Set the remote's url for pushing+ *+ * Existing connections will not be updated.+ *+ * @param remote the remote+ * @param url the url to set or NULL to clear the pushurl+ * @return 0 or an error value+ */+GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url);++/** * Set the remote's fetch refspec * * @param remote the remote@@ -94,13 +156,13 @@ * @param remote the remote * @return a pointer to the fetch refspec or NULL if it doesn't exist */-GIT_EXTERN(const git_refspec *) git_remote_fetchspec(git_remote *remote);+GIT_EXTERN(const git_refspec *) git_remote_fetchspec(const git_remote *remote); /** * Set the remote's push refspec * * @param remote the remote- * @apram spec the new push refspec+ * @param spec the new push refspec * @return 0 or an error value */ GIT_EXTERN(int) git_remote_set_pushspec(git_remote *remote, const char *spec);@@ -112,7 +174,7 @@ * @return a pointer to the push refspec or NULL if it doesn't exist */ -GIT_EXTERN(const git_refspec *) git_remote_pushspec(git_remote *remote);+GIT_EXTERN(const git_refspec *) git_remote_pushspec(const git_remote *remote); /** * Open a connection to a remote@@ -125,7 +187,7 @@ * @param direction whether you want to receive or send data * @return 0 or an error code */-GIT_EXTERN(int) git_remote_connect(git_remote *remote, int direction);+GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction); /** * Get a list of refs at the remote@@ -133,9 +195,13 @@ * The remote (or more exactly its transport) must be connected. The * memory belongs to the remote. *- * @param refs where to store the refs+ * If you a return a non-zero value from the callback, this will stop+ * looping over the refs.+ * * @param remote the remote- * @return 0 or an error code+ * @param list_cb function to call with each ref discovered at the remote+ * @param payload additional data to pass to the callback+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload); @@ -149,10 +215,16 @@ * filename will be NULL and the function will return success. * * @param remote the remote to download from- * @param filename where to store the temproray filename+ * @param progress_cb function to call with progress information. Be aware that+ * this is called inline with network and indexing operations, so performance+ * may be affected.+ * @param progress_payload payload for the progress callback * @return 0 or an error code */-GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats);+GIT_EXTERN(int) git_remote_download(+ git_remote *remote,+ git_transfer_progress_callback progress_cb,+ void *payload); /** * Check whether the remote is connected@@ -160,11 +232,22 @@ * Check whether the remote's underlying transport is connected to the * remote host. *+ * @param remote the remote * @return 1 if it's connected, 0 otherwise. */ GIT_EXTERN(int) git_remote_connected(git_remote *remote); /**+ * Cancel the operation+ *+ * At certain points in its operation, the network code checks whether+ * the operation has been cancelled and if so stops the operation.+ *+ * @param remote the remote+ */+GIT_EXTERN(void) git_remote_stop(git_remote *remote);++/** * Disconnect from the remote * * Close the connection to the remote and free the underlying@@ -188,14 +271,14 @@ * Update the tips to the new state * * @param remote the remote to update- * @param cb callback to run on each ref update. 'a' is the old value, 'b' is then new value+ * @return 0 or an error code */-GIT_EXTERN(int) git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b));+GIT_EXTERN(int) git_remote_update_tips(git_remote *remote); /** * Return whether a string is a valid remote URL *- * @param tranport the url to check+ * @param url the url to check * @param 1 if the url is valid, 0 otherwise */ GIT_EXTERN(int) git_remote_valid_url(const char *url);@@ -213,21 +296,167 @@ * * The string array must be freed by the user. *- * @param remotes_list a string array with the names of the remotes+ * @param out a string array which receives the names of the remotes * @param repo the repository to query * @return 0 or an error code */-GIT_EXTERN(int) git_remote_list(git_strarray *remotes_list, git_repository *repo);+GIT_EXTERN(int) git_remote_list(git_strarray *out, git_repository *repo); /**- * Add a remote with the default fetch refspec to the repository's configuration+ * Choose whether to check the server's certificate (applies to HTTPS only) *- * @param out the resulting remote- * @param repo the repository in which to create the remote- * @param name the remote's name- * @param url the remote's url+ * @param remote the remote to configure+ * @param check whether to check the server's certificate (defaults to yes) */-GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url);+GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);++/**+ * Set a credentials acquisition callback for this remote. If the remote is+ * not available for anonymous access, then you must set this callback in order+ * to provide credentials to the transport at the time of authentication+ * failure so that retry can be performed.+ *+ * @param remote the remote to configure+ * @param cred_acquire_cb The credentials acquisition callback to use (defaults+ * to NULL)+ */+GIT_EXTERN(void) git_remote_set_cred_acquire_cb(+ git_remote *remote,+ git_cred_acquire_cb cred_acquire_cb,+ void *payload);++/**+ * Sets a custom transport for the remote. The caller can use this function+ * to bypass the automatic discovery of a transport by URL scheme (i.e.+ * http://, https://, git://) and supply their own transport to be used+ * instead. After providing the transport to a remote using this function,+ * the transport object belongs exclusively to that remote, and the remote will+ * free it when it is freed with git_remote_free.+ *+ * @param remote the remote to configure+ * @param transport the transport object for the remote to use+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_remote_set_transport(+ git_remote *remote,+ git_transport *transport);++/**+ * Argument to the completion callback which tells it which operation+ * finished.+ */+typedef enum git_remote_completion_type {+ GIT_REMOTE_COMPLETION_DOWNLOAD,+ GIT_REMOTE_COMPLETION_INDEXING,+ GIT_REMOTE_COMPLETION_ERROR,+} git_remote_completion_type;++/**+ * The callback settings structure+ *+ * Set the calbacks to be called by the remote.+ */+struct git_remote_callbacks {+ unsigned int version;+ void (*progress)(const char *str, int len, void *data);+ int (*completion)(git_remote_completion_type type, void *data);+ int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data);+ void *payload;+};++#define GIT_REMOTE_CALLBACKS_VERSION 1+#define GIT_REMOTE_CALLBACKS_INIT {GIT_REMOTE_CALLBACKS_VERSION}++/**+ * Set the callbacks for a remote+ *+ * Note that the remote keeps its own copy of the data and you need to+ * call this function again if you want to change the callbacks.+ *+ * @param remote the remote to configure+ * @param callbacks a pointer to the user's callback settings+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks);++/**+ * Get the statistics structure that is filled in by the fetch operation.+ */+GIT_EXTERN(const git_transfer_progress *) git_remote_stats(git_remote *remote);++typedef enum {+ GIT_REMOTE_DOWNLOAD_TAGS_UNSET,+ GIT_REMOTE_DOWNLOAD_TAGS_NONE,+ GIT_REMOTE_DOWNLOAD_TAGS_AUTO,+ GIT_REMOTE_DOWNLOAD_TAGS_ALL+} git_remote_autotag_option_t;++/**+ * Retrieve the tag auto-follow setting+ *+ * @param remote the remote to query+ * @return the auto-follow setting+ */+GIT_EXTERN(git_remote_autotag_option_t) git_remote_autotag(git_remote *remote);++/**+ * Set the tag auto-follow setting+ *+ * @param remote the remote to configure+ * @param value a GIT_REMOTE_DOWNLOAD_TAGS value+ */+GIT_EXTERN(void) git_remote_set_autotag(+ git_remote *remote,+ git_remote_autotag_option_t value);++/**+ * Give the remote a new name+ *+ * All remote-tracking branches and configuration settings+ * for the remote are updated.+ *+ * The new name will be checked for validity.+ * See `git_tag_create()` for rules about valid names.+ *+ * A temporary in-memory remote cannot be given a name with this method.+ *+ * @param remote the remote to rename+ * @param new_name the new name the remote should bear+ * @param callback Optional callback to notify the consumer of fetch refspecs+ * that haven't been automatically updated and need potential manual tweaking.+ * @param payload Additional data to pass to the callback+ * @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code+ */+GIT_EXTERN(int) git_remote_rename(+ git_remote *remote,+ const char *new_name,+ git_remote_rename_problem_cb callback,+ void *payload);++/**+ * Retrieve the update FETCH_HEAD setting.+ *+ * @param remote the remote to query+ * @return the update FETCH_HEAD setting+ */+GIT_EXTERN(int) git_remote_update_fetchhead(git_remote *remote);++/**+ * Sets the update FETCH_HEAD setting. By default, FETCH_HEAD will be+ * updated on every fetch. Set to 0 to disable.+ *+ * @param remote the remote to configure+ * @param value 0 to disable updating FETCH_HEAD+ */+GIT_EXTERN(void) git_remote_set_update_fetchhead(git_remote *remote, int value);++/**+ * Ensure the remote name is well-formed.+ *+ * @param remote_name name to be checked.+ * @return 1 if the reference name is acceptable; 0 if it isn't+ */+GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name); /** @} */ GIT_END_DECL
libgit2/include/git2/repository.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -29,13 +29,26 @@ * The method will automatically detect if 'path' is a normal * or bare repository or fail is 'path' is neither. *- * @param repository pointer to the repo which will be opened+ * @param out pointer to the repo which will be opened * @param path the path to the repository * @return 0 or an error code */-GIT_EXTERN(int) git_repository_open(git_repository **repository, const char *path);+GIT_EXTERN(int) git_repository_open(git_repository **out, const char *path); /**+ * Create a "fake" repository to wrap an object database+ *+ * Create a repository object to wrap an object database to be used+ * with the API when all you have is an object database. This doesn't+ * have any paths associated with it, so use with care.+ *+ * @param out pointer to the repo+ * @param odb the object database to wrap+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_repository_wrap_odb(git_repository **out, git_odb *odb);++/** * Look for a git repository and copy its path in the given buffer. * The lookup start from base_path and walk across parent directories * if nothing has been found. The lookup ends when the first repository@@ -45,10 +58,10 @@ * The method will automatically detect if the repository is bare * (if there is a repository). *- * @param repository_path The user allocated buffer which will+ * @param path_out The user allocated buffer which will * contain the found path. *- * @param size repository_path size+ * @param path_size repository_path size * * @param start_path The base path where the lookup starts. *@@ -64,24 +77,50 @@ * @return 0 or an error code */ GIT_EXTERN(int) git_repository_discover(- char *repository_path,- size_t size,+ char *path_out,+ size_t path_size, const char *start_path, int across_fs, const char *ceiling_dirs); -enum {+/**+ * Option flags for `git_repository_open_ext`.+ *+ * * GIT_REPOSITORY_OPEN_NO_SEARCH - Only open the repository if it can be+ * immediately found in the start_path. Do not walk up from the+ * start_path looking at parent directories.+ * * GIT_REPOSITORY_OPEN_CROSS_FS - Unless this flag is set, open will not+ * continue searching across filesystem boundaries (i.e. when `st_dev`+ * changes from the `stat` system call). (E.g. Searching in a user's home+ * directory "/home/user/source/" will not return "/.git/" as the found+ * repo if "/" is a different filesystem than "/home".)+ */+typedef enum { GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0), GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1),-};+} git_repository_open_flag_t; /** * Find and open a repository with extended controls.+ *+ * @param out Pointer to the repo which will be opened. This can+ * actually be NULL if you only want to use the error code to+ * see if a repo at this path could be opened.+ * @param path Path to open as git repository. If the flags+ * permit "searching", then this can be a path to a subdirectory+ * inside the working directory of the repository.+ * @param flags A combination of the GIT_REPOSITORY_OPEN flags above.+ * @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR delimited list of path+ * prefixes at which the search for a containing repository should+ * terminate.+ * @return 0 on success, GIT_ENOTFOUND if no repository could be found,+ * or -1 if there was a repository but open failed for some reason+ * (such as repo corruption or system errors). */ GIT_EXTERN(int) git_repository_open_ext(- git_repository **repo,- const char *start_path,- uint32_t flags,+ git_repository **out,+ const char *path,+ unsigned int flags, const char *ceiling_dirs); /**@@ -111,25 +150,148 @@ * TODO: * - Reinit the repository *- * @param repo_out pointer to the repo which will be created or reinitialized+ * @param out pointer to the repo which will be created or reinitialized * @param path the path to the repository- * @param is_bare if true, a Git repository without a working directory is created- * at the pointed path. If false, provided path will be considered as the working- * directory into which the .git directory will be created.+ * @param is_bare if true, a Git repository without a working directory is+ * created at the pointed path. If false, provided path will be+ * considered as the working directory into which the .git directory+ * will be created. * * @return 0 or an error code */-GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare);+GIT_EXTERN(int) git_repository_init(+ git_repository **out,+ const char *path,+ unsigned is_bare); /**+ * Option flags for `git_repository_init_ext`.+ *+ * These flags configure extra behaviors to `git_repository_init_ext`.+ * In every case, the default behavior is the zero value (i.e. flag is+ * not set). Just OR the flag values together for the `flags` parameter+ * when initializing a new repo. Details of individual values are:+ *+ * * BARE - Create a bare repository with no working directory.+ * * NO_REINIT - Return an EEXISTS error if the repo_path appears to+ * already be an git repository.+ * * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo+ * path for non-bare repos (if it is not already there), but+ * passing this flag prevents that behavior.+ * * MKDIR - Make the repo_path (and workdir_path) as needed. Init is+ * always willing to create the ".git" directory even without this+ * flag. This flag tells init to create the trailing component of+ * the repo and workdir paths as needed.+ * * MKPATH - Recursively make all components of the repo and workdir+ * paths as necessary.+ * * EXTERNAL_TEMPLATE - libgit2 normally uses internal templates to+ * initialize a new repo. This flags enables external templates,+ * looking the "template_path" from the options if set, or the+ * `init.templatedir` global config if not, or falling back on+ * "/usr/share/git-core/templates" if it exists.+ */+typedef enum {+ GIT_REPOSITORY_INIT_BARE = (1u << 0),+ GIT_REPOSITORY_INIT_NO_REINIT = (1u << 1),+ GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = (1u << 2),+ GIT_REPOSITORY_INIT_MKDIR = (1u << 3),+ GIT_REPOSITORY_INIT_MKPATH = (1u << 4),+ GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = (1u << 5),+} git_repository_init_flag_t;++/**+ * Mode options for `git_repository_init_ext`.+ *+ * Set the mode field of the `git_repository_init_options` structure+ * either to the custom mode that you would like, or to one of the+ * following modes:+ *+ * * SHARED_UMASK - Use permissions configured by umask - the default.+ * * SHARED_GROUP - Use "--shared=group" behavior, chmod'ing the new repo+ * to be group writable and "g+sx" for sticky group assignment.+ * * SHARED_ALL - Use "--shared=all" behavior, adding world readability.+ * * Anything else - Set to custom value.+ */+typedef enum {+ GIT_REPOSITORY_INIT_SHARED_UMASK = 0,+ GIT_REPOSITORY_INIT_SHARED_GROUP = 0002775,+ GIT_REPOSITORY_INIT_SHARED_ALL = 0002777,+} git_repository_init_mode_t;++/**+ * Extended options structure for `git_repository_init_ext`.+ *+ * This contains extra options for `git_repository_init_ext` that enable+ * additional initialization features. The fields are:+ *+ * * flags - Combination of GIT_REPOSITORY_INIT flags above.+ * * mode - Set to one of the standard GIT_REPOSITORY_INIT_SHARED_...+ * constants above, or to a custom value that you would like.+ * * workdir_path - The path to the working dir or NULL for default (i.e.+ * repo_path parent on non-bare repos). IF THIS IS RELATIVE PATH,+ * IT WILL BE EVALUATED RELATIVE TO THE REPO_PATH. If this is not+ * the "natural" working directory, a .git gitlink file will be+ * created here linking to the repo_path.+ * * description - If set, this will be used to initialize the "description"+ * file in the repository, instead of using the template content.+ * * template_path - When GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE is set,+ * this contains the path to use for the template directory. If+ * this is NULL, the config or default directory options will be+ * used instead.+ * * initial_head - The name of the head to point HEAD at. If NULL, then+ * this will be treated as "master" and the HEAD ref will be set+ * to "refs/heads/master". If this begins with "refs/" it will be+ * used verbatim; otherwise "refs/heads/" will be prefixed.+ * * origin_url - If this is non-NULL, then after the rest of the+ * repository initialization is completed, an "origin" remote+ * will be added pointing to this URL.+ */+typedef struct {+ unsigned int version;+ uint32_t flags;+ uint32_t mode;+ const char *workdir_path;+ const char *description;+ const char *template_path;+ const char *initial_head;+ const char *origin_url;+} git_repository_init_options;++#define GIT_REPOSITORY_INIT_OPTIONS_VERSION 1+#define GIT_REPOSITORY_INIT_OPTIONS_INIT {GIT_REPOSITORY_INIT_OPTIONS_VERSION}++/**+ * Create a new Git repository in the given folder with extended controls.+ *+ * This will initialize a new git repository (creating the repo_path+ * if requested by flags) and working directory as needed. It will+ * auto-detect the case sensitivity of the file system and if the+ * file system supports file mode bits correctly.+ *+ * @param out Pointer to the repo which will be created or reinitialized.+ * @param repo_path The path to the repository.+ * @param opts Pointer to git_repository_init_options struct.+ * @return 0 or an error code on failure.+ */+GIT_EXTERN(int) git_repository_init_ext(+ git_repository **out,+ const char *repo_path,+ git_repository_init_options *opts);++/** * Retrieve and resolve the reference pointed at by HEAD. *- * @param head_out pointer to the reference which will be retrieved+ * The returned `git_reference` will be owned by caller and+ * `git_reference_free()` must be called when done with it to release the+ * allocated memory and prevent a leak.+ *+ * @param out pointer to the reference which will be retrieved * @param repo a repository object *- * @return 0 on success; error code otherwise+ * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing+ * branch, GIT_ENOTFOUND when HEAD is missing; an error code otherwise */-GIT_EXTERN(int) git_repository_head(git_reference **head_out, git_repository *repo);+GIT_EXTERN(int) git_repository_head(git_reference **out, git_repository *repo); /** * Check if a repository's HEAD is detached@@ -138,7 +300,7 @@ * instead of a branch. * * @param repo Repo to test- * @return 1 if HEAD is detached, 0 if i'ts not; error code if there+ * @return 1 if HEAD is detached, 0 if it's not; error code if there * was an error. */ GIT_EXTERN(int) git_repository_head_detached(git_repository *repo);@@ -151,7 +313,7 @@ * * @param repo Repo to test * @return 1 if the current branch is an orphan, 0 if it's not; error- * code if therewas an error+ * code if there was an error */ GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo); @@ -159,7 +321,7 @@ * Check if a repository is empty * * An empty repository has just been initialized and contains- * no commits.+ * no references. * * @param repo Repo to test * @return 1 if the repository is empty, 0 if it isn't, error code@@ -202,9 +364,12 @@ * * @param repo A repository object * @param workdir The path to a working directory+ * @param update_gitlink Create/update gitlink in workdir and set config+ * "core.worktree" (if workdir is not the parent of the .git directory) * @return 0, or an error code */-GIT_EXTERN(int) git_repository_set_workdir(git_repository *repo, const char *workdir);+GIT_EXTERN(int) git_repository_set_workdir(+ git_repository *repo, const char *workdir, int update_gitlink); /** * Check if a repository is bare@@ -277,6 +442,39 @@ GIT_EXTERN(void) git_repository_set_odb(git_repository *repo, git_odb *odb); /**+ * Get the Reference Database Backend for this repository.+ *+ * If a custom refsdb has not been set, the default database for+ * the repository will be returned (the one that manipulates loose+ * and packed references in the `.git` directory).+ *+ * The refdb must be freed once it's no longer being used by+ * the user.+ *+ * @param out Pointer to store the loaded refdb+ * @param repo A repository object+ * @return 0, or an error code+ */+GIT_EXTERN(int) git_repository_refdb(git_refdb **out, git_repository *repo);++/**+ * Set the Reference Database Backend for this repository+ *+ * The refdb will be used for all reference related operations+ * involving this repository.+ *+ * The repository will keep a reference to the refdb; the user+ * must still free the refdb object after setting it to the+ * repository, or it will leak.+ *+ * @param repo A repository object+ * @param refdb An refdb object+ */+GIT_EXTERN(void) git_repository_set_refdb(+ git_repository *repo,+ git_refdb *refdb);++/** * Get the Index file for this repository. * * If a custom index has not been set, the default@@ -306,6 +504,184 @@ * @param index An index object */ GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index);++/**+ * Retrieve git's prepared message+ *+ * Operations such as git revert/cherry-pick/merge with the -n option+ * stop just short of creating a commit with the changes and save+ * their prepared message in .git/MERGE_MSG so the next git-commit+ * execution can present it to the user for them to amend if they+ * wish.+ *+ * Use this function to get the contents of this file. Don't forget to+ * remove the file after you create the commit.+ *+ * @param out Buffer to write data into or NULL to just read required size+ * @param len Length of buffer in bytes+ * @param repo Repository to read prepared message from+ * @return Bytes written to buffer, GIT_ENOTFOUND if no message, or -1 on error+ */+GIT_EXTERN(int) git_repository_message(char *out, size_t len, git_repository *repo);++/**+ * Remove git's prepared message.+ *+ * Remove the message that `git_repository_message` retrieves.+ */+GIT_EXTERN(int) git_repository_message_remove(git_repository *repo);++/**+ * Remove all the metadata associated with an ongoing git merge, including+ * MERGE_HEAD, MERGE_MSG, etc.+ *+ * @param repo A repository object+ * @return 0 on success, or error+ */+GIT_EXTERN(int) git_repository_merge_cleanup(git_repository *repo);++typedef int (*git_repository_fetchhead_foreach_cb)(const char *ref_name,+ const char *remote_url,+ const git_oid *oid,+ unsigned int is_merge,+ void *payload);++/**+ * Call callback 'callback' for each entry in the given FETCH_HEAD file.+ *+ * @param repo A repository object+ * @param callback Callback function+ * @param payload Pointer to callback data (optional)+ * @return 0 on success, GIT_ENOTFOUND, GIT_EUSER or error+ */+GIT_EXTERN(int) git_repository_fetchhead_foreach(git_repository *repo,+ git_repository_fetchhead_foreach_cb callback,+ void *payload);++typedef int (*git_repository_mergehead_foreach_cb)(const git_oid *oid,+ void *payload);++/**+ * If a merge is in progress, call callback 'cb' for each commit ID in the+ * MERGE_HEAD file.+ *+ * @param repo A repository object+ * @param callback Callback function+ * @param apyload Pointer to callback data (optional)+ * @return 0 on success, GIT_ENOTFOUND, GIT_EUSER or error+ */+GIT_EXTERN(int) git_repository_mergehead_foreach(git_repository *repo,+ git_repository_mergehead_foreach_cb callback,+ void *payload);++/**+ * Calculate hash of file using repository filtering rules.+ *+ * If you simply want to calculate the hash of a file on disk with no filters,+ * you can just use the `git_odb_hashfile()` API. However, if you want to+ * hash a file in the repository and you want to apply filtering rules (e.g.+ * crlf filters) before generating the SHA, then use this function.+ *+ * @param out Output value of calculated SHA+ * @param repo Repository pointer+ * @param path Path to file on disk whose contents should be hashed. If the+ * repository is not NULL, this can be a relative path.+ * @param type The object type to hash as (e.g. GIT_OBJ_BLOB)+ * @param as_path The path to use to look up filtering rules. If this is+ * NULL, then the `path` parameter will be used instead. If+ * this is passed as the empty string, then no filters will be+ * applied when calculating the hash.+ */+GIT_EXTERN(int) git_repository_hashfile(+ git_oid *out,+ git_repository *repo,+ const char *path,+ git_otype type,+ const char *as_path);++/**+ * Make the repository HEAD point to the specified reference.+ *+ * If the provided reference points to a Tree or a Blob, the HEAD is+ * unaltered and -1 is returned.+ *+ * If the provided reference points to a branch, the HEAD will point+ * to that branch, staying attached, or become attached if it isn't yet.+ * If the branch doesn't exist yet, no error will be return. The HEAD+ * will then be attached to an unborn branch.+ *+ * Otherwise, the HEAD will be detached and will directly point to+ * the Commit.+ *+ * @param repo Repository pointer+ * @param refname Canonical name of the reference the HEAD should point at+ * @return 0 on success, or an error code+ */+GIT_EXTERN(int) git_repository_set_head(+ git_repository* repo,+ const char* refname);++/**+ * Make the repository HEAD directly point to the Commit.+ *+ * If the provided committish cannot be found in the repository, the HEAD+ * is unaltered and GIT_ENOTFOUND is returned.+ *+ * If the provided commitish cannot be peeled into a commit, the HEAD+ * is unaltered and -1 is returned.+ *+ * Otherwise, the HEAD will eventually be detached and will directly point to+ * the peeled Commit.+ *+ * @param repo Repository pointer+ * @param commitish Object id of the Commit the HEAD should point to+ * @return 0 on success, or an error code+ */+GIT_EXTERN(int) git_repository_set_head_detached(+ git_repository* repo,+ const git_oid* commitish);++/**+ * Detach the HEAD.+ *+ * If the HEAD is already detached and points to a Commit, 0 is returned.+ *+ * If the HEAD is already detached and points to a Tag, the HEAD is+ * updated into making it point to the peeled Commit, and 0 is returned.+ *+ * If the HEAD is already detached and points to a non commitish, the HEAD is + * unaltered, and -1 is returned.+ *+ * Otherwise, the HEAD will be detached and point to the peeled Commit.+ *+ * @param repo Repository pointer+ * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing+ * branch or an error code+ */+GIT_EXTERN(int) git_repository_detach_head(+ git_repository* repo);++typedef enum {+ GIT_REPOSITORY_STATE_NONE,+ GIT_REPOSITORY_STATE_MERGE,+ GIT_REPOSITORY_STATE_REVERT,+ GIT_REPOSITORY_STATE_CHERRY_PICK,+ GIT_REPOSITORY_STATE_BISECT,+ GIT_REPOSITORY_STATE_REBASE,+ GIT_REPOSITORY_STATE_REBASE_INTERACTIVE,+ GIT_REPOSITORY_STATE_REBASE_MERGE,+ GIT_REPOSITORY_STATE_APPLY_MAILBOX,+ GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE,+} git_repository_state_t;++/**+ * Determines the status of a git repository - ie, whether an operation+ * (merge, cherry-pick, etc) is in progress.+ *+ * @param repo Repository pointer+ * @return The state of the repository+ */+GIT_EXTERN(int) git_repository_state(git_repository *repo); /** @} */ GIT_END_DECL
+ libgit2/include/git2/reset.h view
@@ -0,0 +1,85 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_reset_h__+#define INCLUDE_git_reset_h__++#include "common.h"+#include "types.h"+#include "strarray.h"++/**+ * @file git2/reset.h+ * @brief Git reset management routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Kinds of reset operation+ */+typedef enum {+ GIT_RESET_SOFT = 1, /** Move the head to the given commit */+ GIT_RESET_MIXED = 2, /** SOFT plus reset index to the commit */+ GIT_RESET_HARD = 3, /** MIXED plus changes in working tree discarded */+} git_reset_t;++/**+ * Sets the current head to the specified commit oid and optionally+ * resets the index and working tree to match.+ *+ * SOFT reset means the Head will be moved to the commit.+ *+ * MIXED reset will trigger a SOFT reset, plus the index will be replaced+ * with the content of the commit tree.+ *+ * HARD reset will trigger a MIXED reset and the working directory will be+ * replaced with the content of the index. (Untracked and ignored files+ * will be left alone, however.)+ *+ * TODO: Implement remaining kinds of resets.+ *+ * @param repo Repository where to perform the reset operation.+ *+ * @param target Committish to which the Head should be moved to. This object+ * must belong to the given `repo` and can either be a git_commit or a+ * git_tag. When a git_tag is being passed, it should be dereferencable+ * to a git_commit which oid will be used as the target of the branch.+ *+ * @param reset_type Kind of reset operation to perform.+ *+ * @return 0 on success or an error code+ */+GIT_EXTERN(int) git_reset(+ git_repository *repo, git_object *target, git_reset_t reset_type);++/**+ * Updates some entries in the index from the target commit tree.+ *+ * The scope of the updated entries is determined by the paths+ * being passed in the `pathspec` parameters.+ *+ * Passing a NULL `target` will result in removing+ * entries in the index matching the provided pathspecs.+ *+ * @param repo Repository where to perform the reset operation.+ *+ * @param target The committish which content will be used to reset the content+ * of the index.+ *+ * @param pathspecs List of pathspecs to operate on.+ *+ * @return 0 on success or an error code < 0+ */+GIT_EXTERN(int) git_reset_default(+ git_repository *repo,+ git_object *target,+ git_strarray* pathspecs);++/** @} */+GIT_END_DECL+#endif
+ libgit2/include/git2/revparse.h view
@@ -0,0 +1,80 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_revparse_h__+#define INCLUDE_git_revparse_h__++#include "common.h"+#include "types.h"+++/**+ * @file git2/revparse.h+ * @brief Git revision parsing routines+ * @defgroup git_revparse Git revision parsing routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Find a single object, as specified by a revision string. See `man gitrevisions`,+ * or http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for+ * information on the syntax accepted.+ *+ * @param out pointer to output object+ * @param repo the repository to search in+ * @param spec the textual specification for an object+ * @return 0 on success, GIT_ENOTFOUND, GIT_EAMBIGUOUS, GIT_EINVALIDSPEC or an error code+ */+GIT_EXTERN(int) git_revparse_single(git_object **out, git_repository *repo, const char *spec);+++/**+ * Revparse flags. These indicate the intended behavior of the spec passed to+ * git_revparse.+ */+typedef enum {+ /** The spec targeted a single object. */+ GIT_REVPARSE_SINGLE = 1 << 0,+ /** The spec targeted a range of commits. */+ GIT_REVPARSE_RANGE = 1 << 1,+ /** The spec used the '...' operator, which invokes special semantics. */+ GIT_REVPARSE_MERGE_BASE = 1 << 2,+} git_revparse_mode_t;++/**+ * Git Revision Spec: output of a `git_revparse` operation+ */+typedef struct {+ /** The left element of the revspec; must be freed by the user */+ git_object *from;+ /** The right element of the revspec; must be freed by the user */+ git_object *to;+ /** The intent of the revspec */+ unsigned int flags;+} git_revspec;++/**+ * Parse a revision string for `from`, `to`, and intent. See `man gitrevisions` or+ * http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for information+ * on the syntax accepted.+ *+ * @param revspec Pointer to an user-allocated git_revspec struct where the result+ * of the rev-parse will be stored+ * @param repo the repository to search in+ * @param spec the rev-parse spec to parse+ * @return 0 on success, GIT_INVALIDSPEC, GIT_ENOTFOUND, GIT_EAMBIGUOUS or an error code+ */+GIT_EXTERN(int) git_revparse(+ git_revspec *revspec,+ git_repository *repo,+ const char *spec);+++/** @} */+GIT_END_DECL+#endif
libgit2/include/git2/revwalk.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -63,11 +63,11 @@ * it is possible to have several revision walkers in * several different threads walking the same repository. *- * @param walker pointer to the new revision walker+ * @param out pointer to the new revision walker * @param repo the repo to walk through * @return 0 or an error code */-GIT_EXTERN(int) git_revwalk_new(git_revwalk **walker, git_repository *repo);+GIT_EXTERN(int) git_revwalk_new(git_revwalk **out, git_repository *repo); /** * Reset the revision walker for reuse.@@ -92,22 +92,22 @@ * * The given commit will be used as one of the roots * when starting the revision walk. At least one commit- * must be pushed the repository before a walk can+ * must be pushed onto the walker before a walk can * be started. * * @param walk the walker being used for the traversal.- * @param oid the oid of the commit to start from.+ * @param id the oid of the commit to start from. * @return 0 or an error code */-GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *oid);+GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *id); /** * Push matching references *- * The OIDs pinted to by the references that match the given glob+ * The OIDs pointed to by the references that match the given glob * pattern will be pushed to the revision walker. *- * A leading 'refs/' is implied it not present as well as a trailing+ * A leading 'refs/' is implied if not present as well as a trailing * '/ *' if the glob lacks '?', '*' or '['. * * @param walk the walker being used for the traversal@@ -134,19 +134,19 @@ * output on the revision walk. * * @param walk the walker being used for the traversal.- * @param oid the oid of commit that will be ignored during the traversal+ * @param commit_id the oid of commit that will be ignored during the traversal * @return 0 or an error code */-GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *oid);+GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *commit_id); /** * Hide matching references. *- * The OIDs pinted to by the references that match the given glob+ * The OIDs pointed to by the references that match the given glob * pattern and their ancestors will be hidden from the output on the * revision walk. *- * A leading 'refs/' is implied it not present as well as a trailing+ * A leading 'refs/' is implied if not present as well as a trailing * '/ *' if the glob lacks '?', '*' or '['. * * @param walk the walker being used for the traversal@@ -169,7 +169,7 @@ * The reference must point to a commit. * * @param walk the walker being used for the traversal- * @param refname the referece to push+ * @param refname the reference to push * @return 0 or an error code */ GIT_EXTERN(int) git_revwalk_push_ref(git_revwalk *walk, const char *refname);@@ -180,7 +180,7 @@ * The reference must point to a commit. * * @param walk the walker being used for the traversal- * @param refname the referece to hide+ * @param refname the reference to hide * @return 0 or an error code */ GIT_EXTERN(int) git_revwalk_hide_ref(git_revwalk *walk, const char *refname);@@ -198,12 +198,12 @@ * * The revision walker is reset when the walk is over. *- * @param oid Pointer where to store the oid of the next commit+ * @param out Pointer where to store the oid of the next commit * @param walk the walker to pop the commit from. * @return 0 if the next commit was found;- * GIT_REVWALKOVER if there are no commits left to iterate+ * GIT_ITEROVER if there are no commits left to iterate */-GIT_EXTERN(int) git_revwalk_next(git_oid *oid, git_revwalk *walk);+GIT_EXTERN(int) git_revwalk_next(git_oid *out, git_revwalk *walk); /** * Change the sorting mode when iterating through the@@ -215,6 +215,21 @@ * @param sort_mode combination of GIT_SORT_XXX flags */ GIT_EXTERN(void) git_revwalk_sorting(git_revwalk *walk, unsigned int sort_mode);++/**+ * Push and hide the respective endpoints of the given range.+ *+ * The range should be of the form+ * <commit>..<commit>+ * where each <commit> is in the form accepted by 'git_revparse_single'.+ * The left-hand commit will be hidden and the right-hand commit pushed.+ *+ * @param walk the walker being used for the traversal+ * @param range the range+ * @return 0 or an error code+ *+ */+GIT_EXTERN(int) git_revwalk_push_range(git_revwalk *walk, const char *range); /** * Free a revision walker previously allocated.
libgit2/include/git2/signature.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -20,41 +20,52 @@ GIT_BEGIN_DECL /**- * Create a new action signature. The signature must be freed- * manually or using git_signature_free+ * Create a new action signature. *- * @param sig_out new signature, in case of error NULL+ * Call `git_signature_free()` to free the data.+ *+ * Note: angle brackets ('<' and '>') characters are not allowed+ * to be used in either the `name` or the `email` parameter.+ *+ * @param out new signature, in case of error NULL * @param name name of the person * @param email email of the person * @param time time when the action happened * @param offset timezone offset in minutes for the time * @return 0 or an error code */-GIT_EXTERN(int) git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset);+GIT_EXTERN(int) git_signature_new(git_signature **out, const char *name, const char *email, git_time_t time, int offset); /**- * Create a new action signature with a timestamp of 'now'. The- * signature must be freed manually or using git_signature_free+ * Create a new action signature with a timestamp of 'now'. *- * @param sig_out new signature, in case of error NULL+ * Call `git_signature_free()` to free the data.+ *+ * @param out new signature, in case of error NULL * @param name name of the person * @param email email of the person * @return 0 or an error code */-GIT_EXTERN(int) git_signature_now(git_signature **sig_out, const char *name, const char *email);+GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const char *email); /**- * Create a copy of an existing signature.+ * Create a copy of an existing signature. All internal strings are also+ * duplicated. *- * All internal strings are also duplicated.+ * Call `git_signature_free()` to free the data.+ * * @param sig signature to duplicated * @return a copy of sig, NULL on out of memory */ GIT_EXTERN(git_signature *) git_signature_dup(const git_signature *sig); /**- * Free an existing signature+ * Free an existing signature.+ *+ * Because the signature is not an opaque structure, it is legal to free it+ * manually, but be sure to free the "name" and "email" strings in addition+ * to the structure itself. * * @param sig signature to free */
+ libgit2/include/git2/stash.h view
@@ -0,0 +1,122 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_stash_h__+#define INCLUDE_git_stash_h__++#include "common.h"+#include "types.h"+#include "oid.h"++/**+ * @file git2/stash.h+ * @brief Git stash management routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++typedef enum {+ GIT_STASH_DEFAULT = 0,++ /* All changes already added to the index+ * are left intact in the working directory+ */+ GIT_STASH_KEEP_INDEX = (1 << 0),++ /* All untracked files are also stashed and then+ * cleaned up from the working directory+ */+ GIT_STASH_INCLUDE_UNTRACKED = (1 << 1),++ /* All ignored files are also stashed and then+ * cleaned up from the working directory+ */+ GIT_STASH_INCLUDE_IGNORED = (1 << 2),+} git_stash_flags;++/**+ * Save the local modifications to a new stash.+ *+ * @param out Object id of the commit containing the stashed state.+ * This commit is also the target of the direct reference refs/stash.+ *+ * @param repo The owning repository.+ *+ * @param stasher The identity of the person performing the stashing.+ *+ * @param message Optional description along with the stashed state.+ *+ * @param flags Flags to control the stashing process. (see GIT_STASH_* above)+ *+ * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash,+ * or error code.+ */+GIT_EXTERN(int) git_stash_save(+ git_oid *out,+ git_repository *repo,+ git_signature *stasher,+ const char *message,+ unsigned int flags);++/**+ * When iterating over all the stashed states, callback that will be+ * issued per entry.+ *+ * @param index The position within the stash list. 0 points to the+ * most recent stashed state.+ *+ * @param message The stash message.+ *+ * @param stash_id The commit oid of the stashed state.+ *+ * @param payload Extra parameter to callback function.+ *+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code+ */+typedef int (*git_stash_cb)(+ size_t index,+ const char* message,+ const git_oid *stash_id,+ void *payload);++/**+ * Loop over all the stashed states and issue a callback for each one.+ *+ * If the callback returns a non-zero value, this will stop looping.+ *+ * @param repo Repository where to find the stash.+ *+ * @param callabck Callback to invoke per found stashed state. The most recent+ * stash state will be enumerated first.+ *+ * @param payload Extra parameter to callback function.+ *+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code+ */+GIT_EXTERN(int) git_stash_foreach(+ git_repository *repo,+ git_stash_cb callback,+ void *payload);++/**+ * Remove a single stashed state from the stash list.+ *+ * @param repo The owning repository.+ *+ * @param index The position within the stash list. 0 points to the+ * most recent stashed state.+ *+ * @return 0 on success, or error code+ */++GIT_EXTERN(int) git_stash_drop(+ git_repository *repo,+ size_t index);++/** @} */+GIT_END_DECL+#endif
libgit2/include/git2/status.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -9,6 +9,7 @@ #include "common.h" #include "types.h"+#include "strarray.h" /** * @file git2/status.h@@ -19,38 +20,67 @@ */ GIT_BEGIN_DECL -enum {- GIT_STATUS_CURRENT = 0,+/**+ * Status flags for a single file.+ *+ * A combination of these values will be returned to indicate the status of+ * a file. Status compares the working directory, the index, and the+ * current HEAD of the repository. The `GIT_STATUS_INDEX` set of flags+ * represents the status of file in the index relative to the HEAD, and the+ * `GIT_STATUS_WT` set of flags represent the status of the file in the+ * working directory relative to the index.+ */+typedef enum {+ GIT_STATUS_CURRENT = 0, - GIT_STATUS_INDEX_NEW = (1 << 0),- GIT_STATUS_INDEX_MODIFIED = (1 << 1),- GIT_STATUS_INDEX_DELETED = (1 << 2),+ GIT_STATUS_INDEX_NEW = (1u << 0),+ GIT_STATUS_INDEX_MODIFIED = (1u << 1),+ GIT_STATUS_INDEX_DELETED = (1u << 2),+ GIT_STATUS_INDEX_RENAMED = (1u << 3),+ GIT_STATUS_INDEX_TYPECHANGE = (1u << 4), - GIT_STATUS_WT_NEW = (1 << 3),- GIT_STATUS_WT_MODIFIED = (1 << 4),- GIT_STATUS_WT_DELETED = (1 << 5),+ GIT_STATUS_WT_NEW = (1u << 7),+ GIT_STATUS_WT_MODIFIED = (1u << 8),+ GIT_STATUS_WT_DELETED = (1u << 9),+ GIT_STATUS_WT_TYPECHANGE = (1u << 10), - GIT_STATUS_IGNORED = (1 << 6),-};+ GIT_STATUS_IGNORED = (1u << 14),+} git_status_t; /**+ * Function pointer to receive status on individual files+ *+ * `path` is the relative path to the file from the root of the repository.+ *+ * `status_flags` is a combination of `git_status_t` values that apply.+ *+ * `payload` is the value you passed to the foreach function as payload.+ */+typedef int (*git_status_cb)(+ const char *path, unsigned int status_flags, void *payload);++/** * Gather file statuses and run a callback for each one. *- * The callback is passed the path of the file, the status and the data- * pointer passed to this function. If the callback returns something other- * than 0, this function will return that value.+ * The callback is passed the path of the file, the status (a combination of+ * the `git_status_t` values above) and the `payload` data pointer passed+ * into this function. *- * @param repo a repository object- * @param callback the function to call on each file- * @return 0 on success or the return value of the callback that was non-zero+ * If the callback returns a non-zero value, this function will stop looping+ * and return GIT_EUSER.+ *+ * @param repo A repository object+ * @param callback The function to call on each file+ * @param payload Pointer to pass through to callback function+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_status_foreach( git_repository *repo,- int (*callback)(const char *, unsigned int, void *),+ git_status_cb callback, void *payload); /**- * Select the files on which to report status.+ * For extended status, select the files on which to report status. * * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the * rough equivalent of `git status --porcelain` where each file@@ -78,62 +108,108 @@ /** * Flags to control status callbacks *- * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should- * be made on untracked files. These will only be made if the- * workdir files are included in the status "show" option.- * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should- * get callbacks. Again, these callbacks will only be made if- * the workdir files are included in the status "show" option.- * Right now, there is no option to include all files in- * directories that are ignored completely.- * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback- * should be made even on unmodified files.- * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories- * which appear to be submodules should just be skipped over.- * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the- * contents of untracked directories should be included in the- * status. Normally if an entire directory is new, then just- * the top-level directory will be included (with a trailing- * slash on the entry name). Given this flag, the directory- * itself will not be included, but all the files in it will.+ * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made+ * on untracked files. These will only be made if the workdir files are+ * included in the status "show" option.+ * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should get+ * callbacks. Again, these callbacks will only be made if the workdir+ * files are included in the status "show" option. Right now, there is+ * no option to include all files in directories that are ignored+ * completely.+ * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be+ * made even on unmodified files.+ * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories which+ * appear to be submodules should just be skipped over.+ * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the contents of+ * untracked directories should be included in the status. Normally if+ * an entire directory is new, then just the top-level directory will be+ * included (with a trailing slash on the entry name). Given this flag,+ * the directory itself will not be included, but all the files in it+ * will.+ * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path+ * will be treated as a literal path, and not as a pathspec.+ * - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of+ * ignored directories should be included in the status. This is like+ * doing `git ls-files -o -i --exclude-standard` with core git.+ *+ * Calling `git_status_foreach()` is like calling the extended version+ * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,+ * and GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS. Those options are bundled+ * together as `GIT_STATUS_OPT_DEFAULTS` if you want them as a baseline. */+typedef enum {+ GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0),+ GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1),+ GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2),+ GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3),+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4),+ GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5),+ GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6),+} git_status_opt_t; -enum {- GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0),- GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1),- GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),- GIT_STATUS_OPT_EXCLUDE_SUBMODULED = (1 << 3),- GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),-};+#define GIT_STATUS_OPT_DEFAULTS \+ (GIT_STATUS_OPT_INCLUDE_IGNORED | \+ GIT_STATUS_OPT_INCLUDE_UNTRACKED | \+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) /**- * Options to control how callbacks will be made by- * `git_status_foreach_ext()`.+ * Options to control how `git_status_foreach_ext()` will issue callbacks.+ *+ * This structure is set so that zeroing it out will give you relatively+ * sane defaults.+ *+ * The `show` value is one of the `git_status_show_t` constants that+ * control which files to scan and in what order.+ *+ * The `flags` value is an OR'ed combination of the `git_status_opt_t`+ * values above.+ *+ * The `pathspec` is an array of path patterns to match (using+ * fnmatch-style matching), or just an array of paths to match exactly if+ * `GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH` is specified in the flags. */ typedef struct {+ unsigned int version; git_status_show_t show;- unsigned int flags;- git_strarray pathspec;+ unsigned int flags;+ git_strarray pathspec; } git_status_options; +#define GIT_STATUS_OPTIONS_VERSION 1+#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION}+ /** * Gather file status information and run callbacks as requested.+ *+ * This is an extended version of the `git_status_foreach()` API that+ * allows for more granular control over which paths will be processed and+ * in what order. See the `git_status_options` structure for details+ * about the additional controls that this makes available.+ *+ * @param repo Repository object+ * @param opts Status options structure+ * @param callback The function to call on each file+ * @param payload Pointer to pass through to callback function+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_status_foreach_ext( git_repository *repo, const git_status_options *opts,- int (*callback)(const char *, unsigned int, void *),+ git_status_cb callback, void *payload); /**- * Get file status for a single file+ * Get file status for a single file. *- * @param status_flags the status value- * @param repo a repository object- * @param path the file to retrieve status for, rooted at the repo's workdir- * @return GIT_EINVALIDPATH when `path` points at a folder, GIT_ENOTFOUND when- * the file doesn't exist in any of HEAD, the index or the worktree,- * 0 otherwise+ * This is not quite the same as calling `git_status_foreach_ext()` with+ * the pathspec set to the specified path.+ *+ * @param status_flags The status value for the file+ * @param repo A repository object+ * @param path The file to retrieve status for, rooted at the repo's workdir+ * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD,+ * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder,+ * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. */ GIT_EXTERN(int) git_status_file( unsigned int *status_flags,@@ -143,14 +219,16 @@ /** * Test if the ignore rules apply to a given file. *- * This function simply checks the ignore rules to see if they would apply- * to the given file. Unlike git_status_file(), this indicates if the file- * would be ignored regardless of whether the file is already in the index- * or in the repository.+ * This function checks the ignore rules to see if they would apply to the+ * given file. This indicates if the file would be ignored regardless of+ * whether the file is already in the index or committed to the repository. *- * @param ignored boolean returning 0 if the file is not ignored, 1 if it is- * @param repo a repository object- * @param path the file to check ignores for, rooted at the repo's workdir.+ * One way to think of this is if you were to do "git add ." on the+ * directory containing the file, would it be added or not?+ *+ * @param ignored Boolean returning 0 if the file is not ignored, 1 if it is+ * @param repo A repository object+ * @param path The file to check ignores for, rooted at the repo's workdir. * @return 0 if ignore rules could be processed for the file (regardless * of whether it exists or not), or an error < 0 if they could not. */
+ libgit2/include/git2/strarray.h view
@@ -0,0 +1,60 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_strarray_h__+#define INCLUDE_git_strarray_h__++#include "common.h"++/**+ * @file git2/strarray.h+ * @brief Git string array routines+ * @defgroup git_strarray Git string array routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/** Array of strings */+typedef struct git_strarray {+ char **strings;+ size_t count;+} git_strarray;++/**+ * Close a string array object+ *+ * This method should be called on `git_strarray` objects where the strings+ * array is allocated and contains allocated strings, such as what you+ * would get from `git_strarray_copy()`. Not doing so, will result in a+ * memory leak.+ *+ * This does not free the `git_strarray` itself, since the library will+ * never allocate that object directly itself (it is more commonly embedded+ * inside another struct or created on the stack).+ *+ * @param array git_strarray from which to free string data+ */+GIT_EXTERN(void) git_strarray_free(git_strarray *array);++/**+ * Copy a string array object from source to target.+ *+ * Note: target is overwritten and hence should be empty, otherwise its+ * contents are leaked. Call git_strarray_free() if necessary.+ *+ * @param tgt target+ * @param src source+ * @return 0 on success, < 0 on allocation failure+ */+GIT_EXTERN(int) git_strarray_copy(git_strarray *tgt, const git_strarray *src);+++/** @} */+GIT_END_DECL++#endif+
libgit2/include/git2/submodule.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -20,55 +20,178 @@ */ GIT_BEGIN_DECL +/**+ * Opaque structure representing a submodule.+ *+ * Submodule support in libgit2 builds a list of known submodules and keeps+ * it in the repository. The list is built from the .gitmodules file, the+ * .git/config file, the index, and the HEAD tree. Items in the working+ * directory that look like submodules (i.e. a git repo) but are not+ * mentioned in those places won't be tracked.+ */+typedef struct git_submodule git_submodule;++/**+ * Values that could be specified for the update rule of a submodule.+ *+ * Use the DEFAULT value if you have altered the update value via+ * `git_submodule_set_update()` and wish to reset to the original default.+ */ typedef enum {+ GIT_SUBMODULE_UPDATE_DEFAULT = -1, GIT_SUBMODULE_UPDATE_CHECKOUT = 0, GIT_SUBMODULE_UPDATE_REBASE = 1,- GIT_SUBMODULE_UPDATE_MERGE = 2+ GIT_SUBMODULE_UPDATE_MERGE = 2,+ GIT_SUBMODULE_UPDATE_NONE = 3 } git_submodule_update_t; +/**+ * Values that could be specified for how closely to examine the+ * working directory when getting submodule status.+ *+ * Use the DEFUALT value if you have altered the ignore value via+ * `git_submodule_set_ignore()` and wish to reset to the original value.+ */ typedef enum {- GIT_SUBMODULE_IGNORE_ALL = 0, /* never dirty */- GIT_SUBMODULE_IGNORE_DIRTY = 1, /* only dirty if HEAD moved */- GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */- GIT_SUBMODULE_IGNORE_NONE = 3 /* any change or untracked == dirty */+ GIT_SUBMODULE_IGNORE_DEFAULT = -1, /* reset to default */+ GIT_SUBMODULE_IGNORE_NONE = 0, /* any change or untracked == dirty */+ GIT_SUBMODULE_IGNORE_UNTRACKED = 1, /* dirty if tracked files change */+ GIT_SUBMODULE_IGNORE_DIRTY = 2, /* only dirty if HEAD moved */+ GIT_SUBMODULE_IGNORE_ALL = 3 /* never dirty */ } git_submodule_ignore_t; /**- * Description of submodule+ * Return codes for submodule status. *- * This record describes a submodule found in a repository. There- * should be an entry for every submodule found in the HEAD and for- * every submodule described in .gitmodules. The fields are as follows:+ * A combination of these flags will be returned to describe the status of a+ * submodule. Depending on the "ignore" property of the submodule, some of+ * the flags may never be returned because they indicate changes that are+ * supposed to be ignored. *- * - `name` is the name of the submodule from .gitmodules.- * - `path` is the path to the submodule from the repo working directory.- * It is almost always the same as `name`.- * - `url` is the url for the submodule.- * - `oid` is the HEAD SHA1 for the submodule.- * - `update` is a value from above - see gitmodules(5) update.- * - `ignore` is a value from above - see gitmodules(5) ignore.- * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules.- * - `refcount` is for internal use.+ * Submodule info is contained in 4 places: the HEAD tree, the index, config+ * files (both .git/config and .gitmodules), and the working directory. Any+ * or all of those places might be missing information about the submodule+ * depending on what state the repo is in. We consider all four places to+ * build the combination of status flags. *- * If the submodule has been added to .gitmodules but not yet git added,- * then the `oid` will be zero. If the submodule has been deleted, but- * the delete has not been committed yet, then the `oid` will be set, but- * the `url` will be NULL.+ * There are four values that are not really status, but give basic info+ * about what sources of submodule data are available. These will be+ * returned even if ignore is set to "ALL".+ *+ * * IN_HEAD - superproject head contains submodule+ * * IN_INDEX - superproject index contains submodule+ * * IN_CONFIG - superproject gitmodules has submodule+ * * IN_WD - superproject workdir has submodule+ *+ * The following values will be returned so long as ignore is not "ALL".+ *+ * * INDEX_ADDED - in index, not in head+ * * INDEX_DELETED - in head, not in index+ * * INDEX_MODIFIED - index and head don't match+ * * WD_UNINITIALIZED - workdir contains empty directory+ * * WD_ADDED - in workdir, not index+ * * WD_DELETED - in index, not workdir+ * * WD_MODIFIED - index and workdir head don't match+ *+ * The following can only be returned if ignore is "NONE" or "UNTRACKED".+ *+ * * WD_INDEX_MODIFIED - submodule workdir index is dirty+ * * WD_WD_MODIFIED - submodule workdir has modified files+ *+ * Lastly, the following will only be returned for ignore "NONE".+ *+ * * WD_UNTRACKED - wd contains untracked files */-typedef struct {- char *name;- char *path;- char *url;- git_oid oid; /* sha1 of submodule HEAD ref or zero if not committed */- git_submodule_update_t update;- git_submodule_ignore_t ignore;- int fetch_recurse;- int refcount;-} git_submodule;+typedef enum {+ GIT_SUBMODULE_STATUS_IN_HEAD = (1u << 0),+ GIT_SUBMODULE_STATUS_IN_INDEX = (1u << 1),+ GIT_SUBMODULE_STATUS_IN_CONFIG = (1u << 2),+ GIT_SUBMODULE_STATUS_IN_WD = (1u << 3),+ GIT_SUBMODULE_STATUS_INDEX_ADDED = (1u << 4),+ GIT_SUBMODULE_STATUS_INDEX_DELETED = (1u << 5),+ GIT_SUBMODULE_STATUS_INDEX_MODIFIED = (1u << 6),+ GIT_SUBMODULE_STATUS_WD_UNINITIALIZED = (1u << 7),+ GIT_SUBMODULE_STATUS_WD_ADDED = (1u << 8),+ GIT_SUBMODULE_STATUS_WD_DELETED = (1u << 9),+ GIT_SUBMODULE_STATUS_WD_MODIFIED = (1u << 10),+ GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED = (1u << 11),+ GIT_SUBMODULE_STATUS_WD_WD_MODIFIED = (1u << 12),+ GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13),+} git_submodule_status_t; +#define GIT_SUBMODULE_STATUS__IN_FLAGS \+ (GIT_SUBMODULE_STATUS_IN_HEAD | \+ GIT_SUBMODULE_STATUS_IN_INDEX | \+ GIT_SUBMODULE_STATUS_IN_CONFIG | \+ GIT_SUBMODULE_STATUS_IN_WD)++#define GIT_SUBMODULE_STATUS__INDEX_FLAGS \+ (GIT_SUBMODULE_STATUS_INDEX_ADDED | \+ GIT_SUBMODULE_STATUS_INDEX_DELETED | \+ GIT_SUBMODULE_STATUS_INDEX_MODIFIED)++#define GIT_SUBMODULE_STATUS__WD_FLAGS \+ ~(GIT_SUBMODULE_STATUS__IN_FLAGS | GIT_SUBMODULE_STATUS__INDEX_FLAGS)++#define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \+ (((S) & ~GIT_SUBMODULE_STATUS__IN_FLAGS) == 0)++#define GIT_SUBMODULE_STATUS_IS_INDEX_UNMODIFIED(S) \+ (((S) & GIT_SUBMODULE_STATUS__INDEX_FLAGS) == 0)++#define GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(S) \+ (((S) & GIT_SUBMODULE_STATUS__WD_FLAGS) == 0)++#define GIT_SUBMODULE_STATUS_IS_WD_DIRTY(S) \+ (((S) & (GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED | \+ GIT_SUBMODULE_STATUS_WD_WD_MODIFIED | \+ GIT_SUBMODULE_STATUS_WD_UNTRACKED)) != 0)+ /**- * Iterate over all submodules of a repository.+ * Lookup submodule information by name or path. *+ * Given either the submodule name or path (they are usually the same), this+ * returns a structure describing the submodule.+ *+ * There are two expected error scenarios:+ *+ * - The submodule is not mentioned in the HEAD, the index, and the config,+ * but does "exist" in the working directory (i.e. there is a subdirectory+ * that is a valid self-contained git repo). In this case, this function+ * returns GIT_EEXISTS to indicate the the submodule exists but not in a+ * state where a git_submodule can be instantiated.+ * - The submodule is not mentioned in the HEAD, index, or config and the+ * working directory doesn't contain a value git repo at that path.+ * There may or may not be anything else at that path, but nothing that+ * looks like a submodule. In this case, this returns GIT_ENOTFOUND.+ *+ * The submodule object is owned by the containing repo and will be freed+ * when the repo is freed. The caller need not free the submodule.+ *+ * @param submodule Pointer to submodule description object pointer..+ * @param repo The repository.+ * @param name The name of the submodule. Trailing slashes will be ignored.+ * @return 0 on success, GIT_ENOTFOUND if submodule does not exist,+ * GIT_EEXISTS if submodule exists in working directory only, -1 on+ * other errors.+ */+GIT_EXTERN(int) git_submodule_lookup(+ git_submodule **submodule,+ git_repository *repo,+ const char *name);++/**+ * Iterate over all tracked submodules of a repository.+ *+ * See the note on `git_submodule` above. This iterates over the tracked+ * submodules as decribed therein.+ *+ * If you are concerned about items in the working directory that look like+ * submodules but are not tracked, the diff API will generate a diff record+ * for workdir items that look like submodules but are not tracked, showing+ * them as added in the workdir. Also, the status API will treat the entire+ * subdirectory of a contained git repo as a single GIT_STATUS_WT_NEW item.+ * * @param repo The repository * @param callback Function to be called with the name of each submodule. * Return a non-zero value to terminate the iteration.@@ -77,26 +200,344 @@ */ GIT_EXTERN(int) git_submodule_foreach( git_repository *repo,- int (*callback)(const char *name, void *payload),+ int (*callback)(git_submodule *sm, const char *name, void *payload), void *payload); /**- * Lookup submodule information by name or path.+ * Set up a new git submodule for checkout. *- * Given either the submodule name or path (they are ususally the same),- * this returns a structure describing the submodule. If the submodule- * does not exist, this will return GIT_ENOTFOUND and set the submodule- * pointer to NULL.+ * This does "git submodule add" up to the fetch and checkout of the+ * submodule contents. It preps a new submodule, creates an entry in+ * .gitmodules and creates an empty initialized repository either at the+ * given path in the working directory or in .git/modules with a gitlink+ * from the working directory to the new repo. *- * @param submodule Pointer to submodule description object pointer..- * @param repo The repository.- * @param name The name of the submodule. Trailing slashes will be ignored.- * @return 0 on success, GIT_ENOTFOUND if submodule does not exist, -1 on error+ * To fully emulate "git submodule add" call this function, then open the+ * submodule repo and perform the clone step as needed. Lastly, call+ * `git_submodule_add_finalize()` to wrap up adding the new submodule and+ * .gitmodules to the index to be ready to commit.+ *+ * @param submodule The newly created submodule ready to open for clone+ * @param repo Superproject repository to contain the new submodule+ * @param url URL for the submodules remote+ * @param path Path at which the submodule should be created+ * @param use_gitlink Should workdir contain a gitlink to the repo in+ * .git/modules vs. repo directly in workdir.+ * @return 0 on success, GIT_EEXISTS if submodule already exists,+ * -1 on other errors. */-GIT_EXTERN(int) git_submodule_lookup(+GIT_EXTERN(int) git_submodule_add_setup( git_submodule **submodule, git_repository *repo,- const char *name);+ const char *url,+ const char *path,+ int use_gitlink);++/**+ * Resolve the setup of a new git submodule.+ *+ * This should be called on a submodule once you have called add setup+ * and done the clone of the submodule. This adds the .gitmodules file+ * and the newly cloned submodule to the index to be ready to be committed+ * (but doesn't actually do the commit).+ *+ * @param submodule The submodule to finish adding.+ */+GIT_EXTERN(int) git_submodule_add_finalize(git_submodule *submodule);++/**+ * Add current submodule HEAD commit to index of superproject.+ *+ * @param submodule The submodule to add to the index+ * @param write_index Boolean if this should immediately write the index+ * file. If you pass this as false, you will have to get the+ * git_index and explicitly call `git_index_write()` on it to+ * save the change.+ * @return 0 on success, <0 on failure+ */+GIT_EXTERN(int) git_submodule_add_to_index(+ git_submodule *submodule,+ int write_index);++/**+ * Write submodule settings to .gitmodules file.+ *+ * This commits any in-memory changes to the submodule to the gitmodules+ * file on disk. You may also be interested in `git_submodule_init()` which+ * writes submodule info to ".git/config" (which is better for local changes+ * to submodule settings) and/or `git_submodule_sync()` which writes+ * settings about remotes to the actual submodule repository.+ *+ * @param submodule The submodule to write.+ * @return 0 on success, <0 on failure.+ */+GIT_EXTERN(int) git_submodule_save(git_submodule *submodule);++/**+ * Get the containing repository for a submodule.+ *+ * This returns a pointer to the repository that contains the submodule.+ * This is a just a reference to the repository that was passed to the+ * original `git_submodule_lookup()` call, so if that repository has been+ * freed, then this may be a dangling reference.+ *+ * @param submodule Pointer to submodule object+ * @return Pointer to `git_repository`+ */+GIT_EXTERN(git_repository *) git_submodule_owner(git_submodule *submodule);++/**+ * Get the name of submodule.+ *+ * @param submodule Pointer to submodule object+ * @return Pointer to the submodule name+ */+GIT_EXTERN(const char *) git_submodule_name(git_submodule *submodule);++/**+ * Get the path to the submodule.+ *+ * The path is almost always the same as the submodule name, but the+ * two are actually not required to match.+ *+ * @param submodule Pointer to submodule object+ * @return Pointer to the submodule path+ */+GIT_EXTERN(const char *) git_submodule_path(git_submodule *submodule);++/**+ * Get the URL for the submodule.+ *+ * @param submodule Pointer to submodule object+ * @return Pointer to the submodule url+ */+GIT_EXTERN(const char *) git_submodule_url(git_submodule *submodule);++/**+ * Set the URL for the submodule.+ *+ * This sets the URL in memory for the submodule. This will be used for+ * any following submodule actions while this submodule data is in memory.+ *+ * After calling this, you may wish to call `git_submodule_save()` to write+ * the changes back to the ".gitmodules" file and `git_submodule_sync()` to+ * write the changes to the checked out submodule repository.+ *+ * @param submodule Pointer to the submodule object+ * @param url URL that should be used for the submodule+ * @return 0 on success, <0 on failure+ */+GIT_EXTERN(int) git_submodule_set_url(git_submodule *submodule, const char *url);++/**+ * Get the OID for the submodule in the index.+ *+ * @param submodule Pointer to submodule object+ * @return Pointer to git_oid or NULL if submodule is not in index.+ */+GIT_EXTERN(const git_oid *) git_submodule_index_id(git_submodule *submodule);++/**+ * Get the OID for the submodule in the current HEAD tree.+ *+ * @param submodule Pointer to submodule object+ * @return Pointer to git_oid or NULL if submodule is not in the HEAD.+ */+GIT_EXTERN(const git_oid *) git_submodule_head_id(git_submodule *submodule);++/**+ * Get the OID for the submodule in the current working directory.+ *+ * This returns the OID that corresponds to looking up 'HEAD' in the checked+ * out submodule. If there are pending changes in the index or anything+ * else, this won't notice that. You should call `git_submodule_status()`+ * for a more complete picture about the state of the working directory.+ *+ * @param submodule Pointer to submodule object+ * @return Pointer to git_oid or NULL if submodule is not checked out.+ */+GIT_EXTERN(const git_oid *) git_submodule_wd_id(git_submodule *submodule);++/**+ * Get the ignore rule for the submodule.+ *+ * There are four ignore values:+ *+ * - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents+ * of the submodule from a clean checkout to be dirty, including the+ * addition of untracked files. This is the default if unspecified.+ * - **GIT_SUBMODULE_IGNORE_UNTRACKED** examines the contents of the+ * working tree (i.e. call `git_status_foreach()` on the submodule) but+ * UNTRACKED files will not count as making the submodule dirty.+ * - **GIT_SUBMODULE_IGNORE_DIRTY** means to only check if the HEAD of the+ * submodule has moved for status. This is fast since it does not need to+ * scan the working tree of the submodule at all.+ * - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo.+ * The working directory will be consider clean so long as there is a+ * checked out version present.+ */+GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore(+ git_submodule *submodule);++/**+ * Set the ignore rule for the submodule.+ *+ * This sets the ignore rule in memory for the submodule. This will be used+ * for any following actions (such as `git_submodule_status()`) while the+ * submodule is in memory. You should call `git_submodule_save()` if you+ * want to persist the new ignore role.+ *+ * Calling this again with GIT_SUBMODULE_IGNORE_DEFAULT or calling+ * `git_submodule_reload()` will revert the rule to the value that was in the+ * original config.+ *+ * @return old value for ignore+ */+GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore(+ git_submodule *submodule,+ git_submodule_ignore_t ignore);++/**+ * Get the update rule for the submodule.+ */+GIT_EXTERN(git_submodule_update_t) git_submodule_update(+ git_submodule *submodule);++/**+ * Set the update rule for the submodule.+ *+ * This sets the update rule in memory for the submodule. You should call+ * `git_submodule_save()` if you want to persist the new update rule.+ *+ * Calling this again with GIT_SUBMODULE_UPDATE_DEFAULT or calling+ * `git_submodule_reload()` will revert the rule to the value that was in the+ * original config.+ *+ * @return old value for update+ */+GIT_EXTERN(git_submodule_update_t) git_submodule_set_update(+ git_submodule *submodule,+ git_submodule_update_t update);++/**+ * Read the fetchRecurseSubmodules rule for a submodule.+ *+ * This accesses the submodule.<name>.fetchRecurseSubmodules value for+ * the submodule that controls fetching behavior for the submodule.+ *+ * Note that at this time, libgit2 does not honor this setting and the+ * fetch functionality current ignores submodules.+ *+ * @return 0 if fetchRecurseSubmodules is false, 1 if true+ */+GIT_EXTERN(int) git_submodule_fetch_recurse_submodules(+ git_submodule *submodule);++/**+ * Set the fetchRecurseSubmodules rule for a submodule.+ *+ * This sets the submodule.<name>.fetchRecurseSubmodules value for+ * the submodule. You should call `git_submodule_save()` if you want+ * to persist the new value.+ *+ * @param submodule The submodule to modify+ * @param fetch_recurse_submodules Boolean value+ * @return old value for fetchRecurseSubmodules+ */+GIT_EXTERN(int) git_submodule_set_fetch_recurse_submodules(+ git_submodule *submodule,+ int fetch_recurse_submodules);++/**+ * Copy submodule info into ".git/config" file.+ *+ * Just like "git submodule init", this copies information about the+ * submodule into ".git/config". You can use the accessor functions+ * above to alter the in-memory git_submodule object and control what+ * is written to the config, overriding what is in .gitmodules.+ *+ * @param submodule The submodule to write into the superproject config+ * @param overwrite By default, existing entries will not be overwritten,+ * but setting this to true forces them to be updated.+ * @return 0 on success, <0 on failure.+ */+GIT_EXTERN(int) git_submodule_init(git_submodule *submodule, int overwrite);++/**+ * Copy submodule remote info into submodule repo.+ *+ * This copies the information about the submodules URL into the checked out+ * submodule config, acting like "git submodule sync". This is useful if+ * you have altered the URL for the submodule (or it has been altered by a+ * fetch of upstream changes) and you need to update your local repo.+ */+GIT_EXTERN(int) git_submodule_sync(git_submodule *submodule);++/**+ * Open the repository for a submodule.+ *+ * This is a newly opened repository object. The caller is responsible for+ * calling `git_repository_free()` on it when done. Multiple calls to this+ * function will return distinct `git_repository` objects. This will only+ * work if the submodule is checked out into the working directory.+ *+ * @param subrepo Pointer to the submodule repo which was opened+ * @param submodule Submodule to be opened+ * @return 0 on success, <0 if submodule repo could not be opened.+ */+GIT_EXTERN(int) git_submodule_open(+ git_repository **repo,+ git_submodule *submodule);++/**+ * Reread submodule info from config, index, and HEAD.+ *+ * Call this to reread cached submodule information for this submodule if+ * you have reason to believe that it has changed.+ */+GIT_EXTERN(int) git_submodule_reload(git_submodule *submodule);++/**+ * Reread all submodule info.+ *+ * Call this to reload all cached submodule information for the repo.+ */+GIT_EXTERN(int) git_submodule_reload_all(git_repository *repo);++/**+ * Get the status for a submodule.+ *+ * This looks at a submodule and tries to determine the status. It+ * will return a combination of the `GIT_SUBMODULE_STATUS` values above.+ * How deeply it examines the working directory to do this will depend+ * on the `git_submodule_ignore_t` value for the submodule - which can be+ * set either temporarily or permanently with `git_submodule_set_ignore()`.+ *+ * @param status Combination of `GIT_SUBMODULE_STATUS` flags+ * @param submodule Submodule for which to get status+ * @return 0 on success, <0 on error+ */+GIT_EXTERN(int) git_submodule_status(+ unsigned int *status,+ git_submodule *submodule);++/**+ * Get the locations of submodule information.+ *+ * This is a bit like a very lightweight version of `git_submodule_status`.+ * It just returns a made of the first four submodule status values (i.e.+ * the ones like GIT_SUBMODULE_STATUS_IN_HEAD, etc) that tell you where the+ * submodule data comes from (i.e. the HEAD commit, gitmodules file, etc.).+ * This can be useful if you want to know if the submodule is present in the+ * working directory at this point in time, etc.+ *+ * @param status Combination of first four `GIT_SUBMODULE_STATUS` flags+ * @param submodule Submodule for which to get status+ * @return 0 on success, <0 on error+ */+GIT_EXTERN(int) git_submodule_location(+ unsigned int *location_status,+ git_submodule *submodule); /** @} */ GIT_END_DECL
libgit2/include/git2/tag.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -11,6 +11,7 @@ #include "types.h" #include "oid.h" #include "object.h"+#include "strarray.h" /** * @file git2/tag.h@@ -24,14 +25,16 @@ /** * Lookup a tag object from the repository. *- * @param tag pointer to the looked up tag+ * @param out pointer to the looked up tag * @param repo the repo to use when locating the tag. * @param id identity of the tag to locate. * @return 0 or an error code */-GIT_INLINE(int) git_tag_lookup(git_tag **tag, git_repository *repo, const git_oid *id)+GIT_INLINE(int) git_tag_lookup(+ git_tag **out, git_repository *repo, const git_oid *id) {- return git_object_lookup((git_object **)tag, repo, id, (git_otype)GIT_OBJ_TAG);+ return git_object_lookup(+ (git_object **)out, repo, id, (git_otype)GIT_OBJ_TAG); } /**@@ -40,32 +43,33 @@ * * @see git_object_lookup_prefix *- * @param tag pointer to the looked up tag+ * @param out pointer to the looked up tag * @param repo the repo to use when locating the tag. * @param id identity of the tag to locate. * @param len the length of the short identifier * @return 0 or an error code */-GIT_INLINE(int) git_tag_lookup_prefix(git_tag **tag, git_repository *repo, const git_oid *id, unsigned int len)+GIT_INLINE(int) git_tag_lookup_prefix(+ git_tag **out, git_repository *repo, const git_oid *id, size_t len) {- return git_object_lookup_prefix((git_object **)tag, repo, id, len, (git_otype)GIT_OBJ_TAG);+ return git_object_lookup_prefix(+ (git_object **)out, repo, id, len, (git_otype)GIT_OBJ_TAG); } /** * Close an open tag *- * This is a wrapper around git_object_free()+ * You can no longer use the git_tag pointer after this call. *- * IMPORTANT:- * It *is* necessary to call this method when you stop- * using a tag. Failure to do so will cause a memory leak.+ * IMPORTANT: You MUST call this method when you are through with a tag to+ * release memory. Failure to do so will cause a memory leak. * * @param tag the tag to close */ GIT_INLINE(void) git_tag_free(git_tag *tag) {- git_object_free((git_object *) tag);+ git_object_free((git_object *)tag); } @@ -75,7 +79,7 @@ * @param tag a previously loaded tag. * @return object identity for the tag. */-GIT_EXTERN(const git_oid *) git_tag_id(git_tag *tag);+GIT_EXTERN(const git_oid *) git_tag_id(const git_tag *tag); /** * Get the tagged object of a tag@@ -83,11 +87,11 @@ * This method performs a repository lookup for the * given object and returns it *- * @param target pointer where to store the target+ * @param target_out pointer where to store the target * @param tag a previously loaded tag. * @return 0 or an error code */-GIT_EXTERN(int) git_tag_target(git_object **target, git_tag *tag);+GIT_EXTERN(int) git_tag_target(git_object **target_out, const git_tag *tag); /** * Get the OID of the tagged object of a tag@@ -95,7 +99,7 @@ * @param tag a previously loaded tag. * @return pointer to the OID */-GIT_EXTERN(const git_oid *) git_tag_target_oid(git_tag *tag);+GIT_EXTERN(const git_oid *) git_tag_target_id(const git_tag *tag); /** * Get the type of a tag's tagged object@@ -103,7 +107,7 @@ * @param tag a previously loaded tag. * @return type of the tagged object */-GIT_EXTERN(git_otype) git_tag_type(git_tag *tag);+GIT_EXTERN(git_otype) git_tag_target_type(const git_tag *tag); /** * Get the name of a tag@@ -111,23 +115,23 @@ * @param tag a previously loaded tag. * @return name of the tag */-GIT_EXTERN(const char *) git_tag_name(git_tag *tag);+GIT_EXTERN(const char *) git_tag_name(const git_tag *tag); /** * Get the tagger (author) of a tag * * @param tag a previously loaded tag.- * @return reference to the tag's author+ * @return reference to the tag's author or NULL when unspecified */-GIT_EXTERN(const git_signature *) git_tag_tagger(git_tag *tag);+GIT_EXTERN(const git_signature *) git_tag_tagger(const git_tag *tag); /** * Get the message of a tag * * @param tag a previously loaded tag.- * @return message of the tag+ * @return message of the tag or NULL when unspecified */-GIT_EXTERN(const char *) git_tag_message(git_tag *tag);+GIT_EXTERN(const char *) git_tag_message(const git_tag *tag); /**@@ -137,9 +141,13 @@ * this tag object. If `force` is true and a reference * already exists with the given name, it'll be replaced. *- * The message will be cleaned up from excess whitespace- * it will be made sure that the last line ends with a '\n'.+ * The message will not be cleaned up. This can be achieved+ * through `git_message_prettify()`. *+ * The tag name will be checked for validity. You must avoid+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the+ * sequences ".." and "@{" which have special meaning to revparse.+ * * @param oid Pointer where to store the OID of the * newly created tag. If the tag already exists, this parameter * will be the oid of the existing tag, and the function will@@ -161,18 +169,18 @@ * * @param force Overwrite existing references *- * @return 0 or an error code+ * @return 0 on success, GIT_EINVALIDSPEC or an error code * A tag object is written to the ODB, and a proper reference * is written in the /refs/tags folder, pointing to it */ GIT_EXTERN(int) git_tag_create(- git_oid *oid,- git_repository *repo,- const char *tag_name,- const git_object *target,- const git_signature *tagger,- const char *message,- int force);+ git_oid *oid,+ git_repository *repo,+ const char *tag_name,+ const git_object *target,+ const git_signature *tagger,+ const char *message,+ int force); /** * Create a new tag in the repository from a buffer@@ -181,13 +189,13 @@ * @param repo Repository where to store the tag * @param buffer Raw tag data * @param force Overwrite existing tags- * @return 0 on sucess; error code otherwise+ * @return 0 on success; error code otherwise */ GIT_EXTERN(int) git_tag_create_frombuffer(- git_oid *oid,- git_repository *repo,- const char *buffer,- int force);+ git_oid *oid,+ git_repository *repo,+ const char *buffer,+ int force); /** * Create a new lightweight tag pointing at a target object@@ -196,6 +204,9 @@ * this target object. If `force` is true and a reference * already exists with the given name, it'll be replaced. *+ * The tag name will be checked for validity.+ * See `git_tag_create()` for rules about valid names.+ * * @param oid Pointer where to store the OID of the provided * target object. If the tag already exists, this parameter * will be filled with the oid of the existing pointed object@@ -212,30 +223,33 @@ * * @param force Overwrite existing references *- * @return 0 or an error code+ * @return 0 on success, GIT_EINVALIDSPEC or an error code * A proper reference is written in the /refs/tags folder, * pointing to the provided target object */ GIT_EXTERN(int) git_tag_create_lightweight(- git_oid *oid,- git_repository *repo,- const char *tag_name,- const git_object *target,- int force);+ git_oid *oid,+ git_repository *repo,+ const char *tag_name,+ const git_object *target,+ int force); /** * Delete an existing tag reference. *+ * The tag name will be checked for validity.+ * See `git_tag_create()` for rules about valid names.+ * * @param repo Repository where lives the tag * * @param tag_name Name of the tag to be deleted; * this name is validated for consistency. *- * @return 0 or an error code+ * @return 0 on success, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_tag_delete(- git_repository *repo,- const char *tag_name);+ git_repository *repo,+ const char *tag_name); /** * Fill a list with all the tags in the Repository@@ -251,8 +265,8 @@ * @return 0 or an error code */ GIT_EXTERN(int) git_tag_list(- git_strarray *tag_names,- git_repository *repo);+ git_strarray *tag_names,+ git_repository *repo); /** * Fill a list with all the tags in the Repository@@ -273,24 +287,39 @@ * @return 0 or an error code */ GIT_EXTERN(int) git_tag_list_match(- git_strarray *tag_names,- const char *pattern,- git_repository *repo);+ git_strarray *tag_names,+ const char *pattern,+ git_repository *repo); ++typedef int (*git_tag_foreach_cb)(const char *name, git_oid *oid, void *payload);+ /**- * Recursively peel a tag until a non tag git_object- * is met+ * Call callback `cb' for each tag in the repository *+ * @param repo Repository+ * @param callback Callback function+ * @param payload Pointer to callback data (optional)+ */+GIT_EXTERN(int) git_tag_foreach(+ git_repository *repo,+ git_tag_foreach_cb callback,+ void *payload);+++/**+ * Recursively peel a tag until a non tag git_object is found+ * * The retrieved `tag_target` object is owned by the repository * and should be closed with the `git_object_free` method. *- * @param tag_target Pointer to the peeled git_object+ * @param tag_target_out Pointer to the peeled git_object * @param tag The tag to be processed * @return 0 or an error code */ GIT_EXTERN(int) git_tag_peel(- git_object **tag_target,- git_tag *tag);+ git_object **tag_target_out,+ const git_tag *tag); /** @} */ GIT_END_DECL
libgit2/include/git2/threads.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -27,8 +27,10 @@ * * If libgit2 has been built without GIT_THREADS * support, this function is a no-op.+ *+ * @return 0 or an error code */-GIT_EXTERN(void) git_threads_init(void);+GIT_EXTERN(int) git_threads_init(void); /** * Shutdown the threading system.
+ libgit2/include/git2/trace.h view
@@ -0,0 +1,68 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_trace_h__+#define INCLUDE_git_trace_h__++#include "common.h"+#include "types.h"++/**+ * @file git2/trace.h+ * @brief Git tracing configuration routines+ * @defgroup git_trace Git tracing configuration routines+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/**+ * Available tracing levels. When tracing is set to a particular level,+ * callers will be provided tracing at the given level and all lower levels.+ */+typedef enum {+ /** No tracing will be performed. */+ GIT_TRACE_NONE = 0,++ /** Severe errors that may impact the program's execution */+ GIT_TRACE_FATAL = 1,++ /** Errors that do not impact the program's execution */+ GIT_TRACE_ERROR = 2,+ + /** Warnings that suggest abnormal data */+ GIT_TRACE_WARN = 3,++ /** Informational messages about program execution */+ GIT_TRACE_INFO = 4,++ /** Detailed data that allows for debugging */+ GIT_TRACE_DEBUG = 5,++ /** Exceptionally detailed debugging data */+ GIT_TRACE_TRACE = 6+} git_trace_level_t;++/**+ * An instance for a tracing function+ */+typedef void (*git_trace_callback)(git_trace_level_t level, const char *msg);++/**+ * Sets the system tracing configuration to the specified level with the+ * specified callback. When system events occur at a level equal to, or+ * lower than, the given level they will be reported to the given callback.+ *+ * @param level Level to set tracing to+ * @param cb Function to call with trace data+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_trace_set(git_trace_level_t level, git_trace_callback cb);++/** @} */+GIT_END_DECL+#endif+
+ libgit2/include/git2/transport.h view
@@ -0,0 +1,328 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_git_transport_h__+#define INCLUDE_git_transport_h__++#include "indexer.h"+#include "net.h"+#include "types.h"++/**+ * @file git2/transport.h+ * @brief Git transport interfaces and functions+ * @defgroup git_transport interfaces and functions+ * @ingroup Git+ * @{+ */+GIT_BEGIN_DECL++/*+ *** Begin interface for credentials acquisition ***+ */++typedef enum {+ /* git_cred_userpass_plaintext */+ GIT_CREDTYPE_USERPASS_PLAINTEXT = 1,+} git_credtype_t;++/* The base structure for all credential types */+typedef struct git_cred {+ git_credtype_t credtype;+ void (*free)(+ struct git_cred *cred);+} git_cred;++/* A plaintext username and password */+typedef struct git_cred_userpass_plaintext {+ git_cred parent;+ char *username;+ char *password;+} git_cred_userpass_plaintext;++/**+ * Creates a new plain-text username and password credential object.+ * The supplied credential parameter will be internally duplicated.+ *+ * @param out The newly created credential object.+ * @param username The username of the credential.+ * @param password The password of the credential.+ * @return 0 for success or an error code for failure+ */+GIT_EXTERN(int) git_cred_userpass_plaintext_new(+ git_cred **out,+ const char *username,+ const char *password);++/**+ * Signature of a function which acquires a credential object.+ *+ * @param cred The newly created credential object.+ * @param url The resource for which we are demanding a credential.+ * @param username_from_url The username that was embedded in a "user@host"+ * remote url, or NULL if not included.+ * @param allowed_types A bitmask stating which cred types are OK to return.+ * @param payload The payload provided when specifying this callback.+ * @return 0 for success or an error code for failure+ */+typedef int (*git_cred_acquire_cb)(+ git_cred **cred,+ const char *url,+ const char *username_from_url,+ unsigned int allowed_types,+ void *payload);++/*+ *** End interface for credentials acquisition ***+ *** Begin base transport interface ***+ */++typedef enum {+ GIT_TRANSPORTFLAGS_NONE = 0,+ /* If the connection is secured with SSL/TLS, the authenticity+ * of the server certificate should not be verified. */+ GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1+} git_transport_flags_t;++typedef void (*git_transport_message_cb)(const char *str, int len, void *data);++typedef struct git_transport {+ unsigned int version;+ /* Set progress and error callbacks */+ int (*set_callbacks)(struct git_transport *transport,+ git_transport_message_cb progress_cb,+ git_transport_message_cb error_cb,+ void *payload);++ /* Connect the transport to the remote repository, using the given+ * direction. */+ int (*connect)(struct git_transport *transport,+ const char *url,+ git_cred_acquire_cb cred_acquire_cb,+ void *cred_acquire_payload,+ int direction,+ int flags);++ /* This function may be called after a successful call to connect(). The+ * provided callback is invoked for each ref discovered on the remote+ * end. */+ int (*ls)(struct git_transport *transport,+ git_headlist_cb list_cb,+ void *payload);++ /* Executes the push whose context is in the git_push object. */+ int (*push)(struct git_transport *transport, git_push *push);++ /* This function may be called after a successful call to connect(), when+ * the direction is FETCH. The function performs a negotiation to calculate+ * the wants list for the fetch. */+ int (*negotiate_fetch)(struct git_transport *transport,+ git_repository *repo,+ const git_remote_head * const *refs,+ size_t count);++ /* This function may be called after a successful call to negotiate_fetch(),+ * when the direction is FETCH. This function retrieves the pack file for+ * the fetch from the remote end. */+ int (*download_pack)(struct git_transport *transport,+ git_repository *repo,+ git_transfer_progress *stats,+ git_transfer_progress_callback progress_cb,+ void *progress_payload);++ /* Checks to see if the transport is connected */+ int (*is_connected)(struct git_transport *transport);++ /* Reads the flags value previously passed into connect() */+ int (*read_flags)(struct git_transport *transport, int *flags);++ /* Cancels any outstanding transport operation */+ void (*cancel)(struct git_transport *transport);++ /* This function is the reverse of connect() -- it terminates the+ * connection to the remote end. */+ int (*close)(struct git_transport *transport);++ /* Frees/destructs the git_transport object. */+ void (*free)(struct git_transport *transport);+} git_transport;++#define GIT_TRANSPORT_VERSION 1+#define GIT_TRANSPORT_INIT {GIT_TRANSPORT_VERSION}++/**+ * Function to use to create a transport from a URL. The transport database+ * is scanned to find a transport that implements the scheme of the URI (i.e.+ * git:// or http://) and a transport object is returned to the caller.+ *+ * @param out The newly created transport (out)+ * @param owner The git_remote which will own this transport+ * @param url The URL to connect to+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const char *url);++/* Signature of a function which creates a transport */+typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param);++/* Transports which come with libgit2 (match git_transport_cb). The expected+ * value for "param" is listed in-line below. */++/**+ * Create an instance of the dummy transport.+ *+ * @param out The newly created transport (out)+ * @param owner The git_remote which will own this transport+ * @param payload You must pass NULL for this parameter.+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_transport_dummy(+ git_transport **out,+ git_remote *owner,+ /* NULL */ void *payload);++/**+ * Create an instance of the local transport.+ *+ * @param out The newly created transport (out)+ * @param owner The git_remote which will own this transport+ * @param payload You must pass NULL for this parameter.+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_transport_local(+ git_transport **out,+ git_remote *owner,+ /* NULL */ void *payload);++/**+ * Create an instance of the smart transport.+ *+ * @param out The newly created transport (out)+ * @param owner The git_remote which will own this transport+ * @param payload A pointer to a git_smart_subtransport_definition+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_transport_smart(+ git_transport **out,+ git_remote *owner,+ /* (git_smart_subtransport_definition *) */ void *payload);++/*+ *** End of base transport interface ***+ *** Begin interface for subtransports for the smart transport ***+ */++/* The smart transport knows how to speak the git protocol, but it has no+ * knowledge of how to establish a connection between it and another endpoint,+ * or how to move data back and forth. For this, a subtransport interface is+ * declared, and the smart transport delegates this work to the subtransports.+ * Three subtransports are implemented: git, http, and winhttp. (The http and+ * winhttp transports each implement both http and https.) */++/* Subtransports can either be RPC = 0 (persistent connection) or RPC = 1+ * (request/response). The smart transport handles the differences in its own+ * logic. The git subtransport is RPC = 0, while http and winhttp are both+ * RPC = 1. */++/* Actions that the smart transport can ask+ * a subtransport to perform */+typedef enum {+ GIT_SERVICE_UPLOADPACK_LS = 1,+ GIT_SERVICE_UPLOADPACK = 2,+ GIT_SERVICE_RECEIVEPACK_LS = 3,+ GIT_SERVICE_RECEIVEPACK = 4,+} git_smart_service_t;++struct git_smart_subtransport;++/* A stream used by the smart transport to read and write data+ * from a subtransport */+typedef struct git_smart_subtransport_stream {+ /* The owning subtransport */+ struct git_smart_subtransport *subtransport;++ int (*read)(+ struct git_smart_subtransport_stream *stream,+ char *buffer,+ size_t buf_size,+ size_t *bytes_read);++ int (*write)(+ struct git_smart_subtransport_stream *stream,+ const char *buffer,+ size_t len);++ void (*free)(+ struct git_smart_subtransport_stream *stream);+} git_smart_subtransport_stream;++/* An implementation of a subtransport which carries data for the+ * smart transport */+typedef struct git_smart_subtransport {+ int (* action)(+ git_smart_subtransport_stream **out,+ struct git_smart_subtransport *transport,+ const char *url,+ git_smart_service_t action);++ /* Subtransports are guaranteed a call to close() between+ * calls to action(), except for the following two "natural" progressions+ * of actions against a constant URL.+ *+ * 1. UPLOADPACK_LS -> UPLOADPACK+ * 2. RECEIVEPACK_LS -> RECEIVEPACK */+ int (* close)(struct git_smart_subtransport *transport);++ void (* free)(struct git_smart_subtransport *transport);+} git_smart_subtransport;++/* A function which creates a new subtransport for the smart transport */+typedef int (*git_smart_subtransport_cb)(+ git_smart_subtransport **out,+ git_transport* owner);++typedef struct git_smart_subtransport_definition {+ /* The function to use to create the git_smart_subtransport */+ git_smart_subtransport_cb callback;++ /* True if the protocol is stateless; false otherwise. For example,+ * http:// is stateless, but git:// is not. */+ unsigned rpc;+} git_smart_subtransport_definition;++/* Smart transport subtransports that come with libgit2 */++/**+ * Create an instance of the http subtransport. This subtransport+ * also supports https. On Win32, this subtransport may be implemented+ * using the WinHTTP library.+ *+ * @param out The newly created subtransport+ * @param owner The smart transport to own this subtransport+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_smart_subtransport_http(+ git_smart_subtransport **out,+ git_transport* owner);++/**+ * Create an instance of the git subtransport.+ *+ * @param out The newly created subtransport+ * @param owner The smart transport to own this subtransport+ * @return 0 or an error code+ */+GIT_EXTERN(int) git_smart_subtransport_git(+ git_smart_subtransport **out,+ git_transport* owner);++/*+ *** End interface for subtransports for the smart transport ***+ */++/** @} */+GIT_END_DECL+#endif
libgit2/include/git2/tree.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -24,14 +24,15 @@ /** * Lookup a tree object from the repository. *- * @param tree pointer to the looked up tree- * @param repo the repo to use when locating the tree.- * @param id identity of the tree to locate.+ * @param out Pointer to the looked up tree+ * @param repo The repo to use when locating the tree.+ * @param id Identity of the tree to locate. * @return 0 or an error code */-GIT_INLINE(int) git_tree_lookup(git_tree **tree, git_repository *repo, const git_oid *id)+GIT_INLINE(int) git_tree_lookup(+ git_tree **out, git_repository *repo, const git_oid *id) {- return git_object_lookup((git_object **)tree, repo, id, GIT_OBJ_TREE);+ return git_object_lookup((git_object **)out, repo, id, GIT_OBJ_TREE); } /**@@ -46,72 +47,136 @@ * @param len the length of the short identifier * @return 0 or an error code */-GIT_INLINE(int) git_tree_lookup_prefix(git_tree **tree, git_repository *repo, const git_oid *id, unsigned int len)+GIT_INLINE(int) git_tree_lookup_prefix(+ git_tree **out,+ git_repository *repo,+ const git_oid *id,+ size_t len) {- return git_object_lookup_prefix((git_object **)tree, repo, id, len, GIT_OBJ_TREE);+ return git_object_lookup_prefix(+ (git_object **)out, repo, id, len, GIT_OBJ_TREE); } /** * Close an open tree *- * This is a wrapper around git_object_free()+ * You can no longer use the git_tree pointer after this call. *- * IMPORTANT:- * It *is* necessary to call this method when you stop- * using a tree. Failure to do so will cause a memory leak.+ * IMPORTANT: You MUST call this method when you stop using a tree to+ * release memory. Failure to do so will cause a memory leak. *- * @param tree the tree to close+ * @param tree The tree to close */- GIT_INLINE(void) git_tree_free(git_tree *tree) {- git_object_free((git_object *) tree);+ git_object_free((git_object *)tree); } - /** * Get the id of a tree. * * @param tree a previously loaded tree. * @return object identity for the tree. */-GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree);+GIT_EXTERN(const git_oid *) git_tree_id(const git_tree *tree); /**+ * Get the repository that contains the tree.+ *+ * @param tree A previously loaded tree.+ * @return Repository that contains this tree.+ */+GIT_EXTERN(git_repository *) git_tree_owner(const git_tree *tree);++/** * Get the number of entries listed in a tree * * @param tree a previously loaded tree. * @return the number of entries in the tree */-GIT_EXTERN(unsigned int) git_tree_entrycount(git_tree *tree);+GIT_EXTERN(size_t) git_tree_entrycount(const git_tree *tree); /** * Lookup a tree entry by its filename *+ * This returns a git_tree_entry that is owned by the git_tree. You don't+ * have to free it, but you must not use it after the git_tree is released.+ * * @param tree a previously loaded tree. * @param filename the filename of the desired entry * @return the tree entry; NULL if not found */-GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(git_tree *tree, const char *filename);+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(+ git_tree *tree, const char *filename); /** * Lookup a tree entry by its position in the tree *+ * This returns a git_tree_entry that is owned by the git_tree. You don't+ * have to free it, but you must not use it after the git_tree is released.+ * * @param tree a previously loaded tree. * @param idx the position in the entry list * @return the tree entry; NULL if not found */-GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(git_tree *tree, unsigned int idx);+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(+ git_tree *tree, size_t idx); /**- * Get the UNIX file attributes of a tree entry+ * Lookup a tree entry by SHA value. *- * @param entry a tree entry- * @return attributes as an integer+ * This returns a git_tree_entry that is owned by the git_tree. You don't+ * have to free it, but you must not use it after the git_tree is released.+ *+ * Warning: this must examine every entry in the tree, so it is not fast.+ *+ * @param tree a previously loaded tree.+ * @param oid the sha being looked for+ * @return the tree entry; NULL if not found */-GIT_EXTERN(unsigned int) git_tree_entry_attributes(const git_tree_entry *entry);+GIT_EXTERN(const git_tree_entry *) git_tree_entry_byoid(+ const git_tree *tree, const git_oid *oid); /**+ * Retrieve a tree entry contained in a tree or in any of its subtrees,+ * given its relative path.+ *+ * Unlike the other lookup functions, the returned tree entry is owned by+ * the user and must be freed explicitly with `git_tree_entry_free()`.+ *+ * @param out Pointer where to store the tree entry+ * @param root Previously loaded tree which is the root of the relative path+ * @param subtree_path Path to the contained entry+ * @return 0 on success; GIT_ENOTFOUND if the path does not exist+ */+GIT_EXTERN(int) git_tree_entry_bypath(+ git_tree_entry **out,+ git_tree *root,+ const char *path);++/**+ * Duplicate a tree entry+ *+ * Create a copy of a tree entry. The returned copy is owned by the user,+ * and must be freed explicitly with `git_tree_entry_free()`.+ *+ * @param entry A tree entry to duplicate+ * @return a copy of the original entry or NULL on error (alloc failure)+ */+GIT_EXTERN(git_tree_entry *) git_tree_entry_dup(const git_tree_entry *entry);++/**+ * Free a user-owned tree entry+ *+ * IMPORTANT: This function is only needed for tree entries owned by the+ * user, such as the ones returned by `git_tree_entry_dup()` or+ * `git_tree_entry_bypath()`.+ *+ * @param entry The entry to free+ */+GIT_EXTERN(void) git_tree_entry_free(git_tree_entry *entry);++/** * Get the filename of a tree entry * * @param entry a tree entry@@ -136,51 +201,55 @@ GIT_EXTERN(git_otype) git_tree_entry_type(const git_tree_entry *entry); /**- * Convert a tree entry to the git_object it points too.+ * Get the UNIX file attributes of a tree entry *- * @param object pointer to the converted object- * @param repo repository where to lookup the pointed object * @param entry a tree entry- * @return 0 or an error code+ * @return filemode as an integer */-GIT_EXTERN(int) git_tree_entry_to_object(git_object **object_out, git_repository *repo, const git_tree_entry *entry);+GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry); /**- * Write a tree to the ODB from the index file+ * Compare two tree entries *- * This method will scan the index and write a representation- * of its current state back to disk; it recursively creates- * tree objects for each of the subtrees stored in the index,- * but only returns the OID of the root tree. This is the OID- * that can be used e.g. to create a commit.+ * @param e1 first tree entry+ * @param e2 second tree entry+ * @return <0 if e1 is before e2, 0 if e1 == e2, >0 if e1 is after e2+ */+GIT_EXTERN(int) git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2);++/**+ * Convert a tree entry to the git_object it points too. *- * The index instance cannot be bare, and needs to be associated- * to an existing repository.+ * You must call `git_object_free()` on the object when you are done with it. *- * @param oid Pointer where to store the written tree- * @param index Index to write+ * @param object pointer to the converted object+ * @param repo repository where to lookup the pointed object+ * @param entry a tree entry * @return 0 or an error code */-GIT_EXTERN(int) git_tree_create_fromindex(git_oid *oid, git_index *index);+GIT_EXTERN(int) git_tree_entry_to_object(+ git_object **object_out,+ git_repository *repo,+ const git_tree_entry *entry); /** * Create a new tree builder. *- * The tree builder can be used to create or modify- * trees in memory and write them as tree objects to the- * database.+ * The tree builder can be used to create or modify trees in memory and+ * write them as tree objects to the database. *- * If the `source` parameter is not NULL, the tree builder- * will be initialized with the entries of the given tree.+ * If the `source` parameter is not NULL, the tree builder will be+ * initialized with the entries of the given tree. *- * If the `source` parameter is NULL, the tree builder will- * have no entries and will have to be filled manually.+ * If the `source` parameter is NULL, the tree builder will start with no+ * entries and will have to be filled manually. *- * @param builder_p Pointer where to store the tree builder+ * @param out Pointer where to store the tree builder * @param source Source tree to initialize the builder (optional)- * @return 0 on sucess; error code otherwise+ * @return 0 on success; error code otherwise */-GIT_EXTERN(int) git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source);+GIT_EXTERN(int) git_treebuilder_create(+ git_treebuilder **out, const git_tree *source); /** * Clear all the entires in the builder@@ -218,7 +287,8 @@ * @param filename Name of the entry * @return pointer to the entry; NULL if not found */-GIT_EXTERN(const git_tree_entry *) git_treebuilder_get(git_treebuilder *bld, const char *filename);+GIT_EXTERN(const git_tree_entry *) git_treebuilder_get(+ git_treebuilder *bld, const char *filename); /** * Add or update an entry to the builder@@ -226,20 +296,31 @@ * Insert a new entry for `filename` in the builder with the * given attributes. *- * if an entry named `filename` already exists, its attributes+ * If an entry named `filename` already exists, its attributes * will be updated with the given ones. *- * The optional pointer `entry_out` can be used to retrieve a- * pointer to the newly created/updated entry.+ * The optional pointer `out` can be used to retrieve a pointer to+ * the newly created/updated entry. Pass NULL if you do not need it. *- * @param entry_out Pointer to store the entry (optional)+ * No attempt is being made to ensure that the provided oid points+ * to an existing git object in the object database, nor that the+ * attributes make sense regarding the type of the pointed at object.+ *+ * @param out Pointer to store the entry (optional) * @param bld Tree builder * @param filename Filename of the entry * @param id SHA1 oid of the entry- * @param attributes Folder attributes of the entry+ * @param filemode Folder attributes of the entry. This parameter must+ * be valued with one of the following entries: 0040000, 0100644,+ * 0100755, 0120000 or 0160000. * @return 0 or an error code */-GIT_EXTERN(int) git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes);+GIT_EXTERN(int) git_treebuilder_insert(+ const git_tree_entry **out,+ git_treebuilder *bld,+ const char *filename,+ const git_oid *id,+ git_filemode_t filemode); /** * Remove an entry from the builder by its filename@@ -247,78 +328,75 @@ * @param bld Tree builder * @param filename Filename of the entry to remove */-GIT_EXTERN(int) git_treebuilder_remove(git_treebuilder *bld, const char *filename);+GIT_EXTERN(int) git_treebuilder_remove(+ git_treebuilder *bld, const char *filename); +typedef int (*git_treebuilder_filter_cb)(+ const git_tree_entry *entry, void *payload);+ /** * Filter the entries in the tree *- * The `filter` callback will be called for each entry- * in the tree with a pointer to the entry and the- * provided `payload`: if the callback returns 1, the- * entry will be filtered (removed from the builder).+ * The `filter` callback will be called for each entry in the tree with a+ * pointer to the entry and the provided `payload`; if the callback returns+ * non-zero, the entry will be filtered (removed from the builder). * * @param bld Tree builder * @param filter Callback to filter entries+ * @param payload Extra data to pass to filter */-GIT_EXTERN(void) git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload);+GIT_EXTERN(void) git_treebuilder_filter(+ git_treebuilder *bld,+ git_treebuilder_filter_cb filter,+ void *payload); /** * Write the contents of the tree builder as a tree object *- * The tree builder will be written to the given `repo`, and- * it's identifying SHA1 hash will be stored in the `oid`- * pointer.+ * The tree builder will be written to the given `repo`, and its+ * identifying SHA1 hash will be stored in the `id` pointer. *- * @param oid Pointer where to store the written OID- * @param repo Repository where to store the object+ * @param id Pointer to store the OID of the newly written tree+ * @param repo Repository in which to store the object * @param bld Tree builder to write * @return 0 or an error code */-GIT_EXTERN(int) git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld);+GIT_EXTERN(int) git_treebuilder_write(+ git_oid *id, git_repository *repo, git_treebuilder *bld); -/**- * Retrieve a subtree contained in a tree, given its- * relative path.- *- * The returned tree is owned by the repository and- * should be closed with the `git_object_free` method.- *- * @param subtree Pointer where to store the subtree- * @param root A previously loaded tree which will be the root of the relative path- * @param subtree_path Path to the contained subtree- * @return 0 on success; GIT_ENOTFOUND if the path does not lead to a subtree- */-GIT_EXTERN(int) git_tree_get_subtree(git_tree **subtree, git_tree *root, const char *subtree_path); /** Callback for the tree traversal method */-typedef int (*git_treewalk_cb)(const char *root, git_tree_entry *entry, void *payload);+typedef int (*git_treewalk_cb)(+ const char *root, const git_tree_entry *entry, void *payload); /** Tree traversal modes */-enum git_treewalk_mode {+typedef enum { GIT_TREEWALK_PRE = 0, /* Pre-order */ GIT_TREEWALK_POST = 1, /* Post-order */-};+} git_treewalk_mode; /**- * Traverse the entries in a tree and its subtrees in- * post or pre order+ * Traverse the entries in a tree and its subtrees in post or pre order. *- * The entries will be traversed in the specified order,- * children subtrees will be automatically loaded as required,- * and the `callback` will be called once per entry with- * the current (relative) root for the entry and the entry- * data itself.+ * The entries will be traversed in the specified order, children subtrees+ * will be automatically loaded as required, and the `callback` will be+ * called once per entry with the current (relative) root for the entry and+ * the entry data itself. *- * If the callback returns a negative value, the passed entry- * will be skiped on the traversal.+ * If the callback returns a positive value, the passed entry will be+ * skipped on the traversal (in pre mode). A negative value stops the walk. * * @param tree The tree to walk- * @param callback Function to call on each tree entry * @param mode Traversal mode (pre or post-order)+ * @param callback Function to call on each tree entry * @param payload Opaque pointer to be passed on each callback * @return 0 or an error code */-GIT_EXTERN(int) git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload);+GIT_EXTERN(int) git_tree_walk(+ const git_tree *tree,+ git_treewalk_mode mode,+ git_treewalk_cb callback,+ void *payload); /** @} */
libgit2/include/git2/types.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -32,6 +32,9 @@ * stat() functions, for all platforms. */ #include <sys/types.h>+#ifdef __amigaos4__+#include <stdint.h>+#endif #if defined(_MSC_VER) @@ -86,6 +89,15 @@ /** A stream to read/write from the ODB */ typedef struct git_odb_stream git_odb_stream; +/** A stream to write a packfile to the ODB */+typedef struct git_odb_writepack git_odb_writepack;++/** An open refs database handle. */+typedef struct git_refdb git_refdb;++/** A custom backend for refs */+typedef struct git_refdb_backend git_refdb_backend;+ /** * Representation of an existing git repository, * including all its object contents@@ -123,7 +135,7 @@ typedef struct git_config git_config; /** Interface to access a configuration file */-typedef struct git_config_file git_config_file;+typedef struct git_config_backend git_config_backend; /** Representation of a reference log entry */ typedef struct git_reflog_entry git_reflog_entry;@@ -134,6 +146,9 @@ /** Representation of a git note */ typedef struct git_note git_note; +/** Representation of a git packbuilder */+typedef struct git_packbuilder git_packbuilder;+ /** Time in a signature */ typedef struct git_time { git_time_t time; /** time in seconds from epoch */@@ -155,9 +170,7 @@ GIT_REF_INVALID = 0, /** Invalid reference */ GIT_REF_OID = 1, /** A reference which points at an object id */ GIT_REF_SYMBOLIC = 2, /** A reference which points at another reference */- GIT_REF_PACKED = 4,- GIT_REF_HAS_PEEL = 8,- GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC|GIT_REF_PACKED,+ GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC, } git_ref_t; /** Basic type of any Git branch. */@@ -166,10 +179,22 @@ GIT_BRANCH_REMOTE = 2, } git_branch_t; +/** Valid modes for index and tree entries. */+typedef enum {+ GIT_FILEMODE_NEW = 0000000,+ GIT_FILEMODE_TREE = 0040000,+ GIT_FILEMODE_BLOB = 0100644,+ GIT_FILEMODE_BLOB_EXECUTABLE = 0100755,+ GIT_FILEMODE_LINK = 0120000,+ GIT_FILEMODE_COMMIT = 0160000,+} git_filemode_t;+ typedef struct git_refspec git_refspec; typedef struct git_remote git_remote;+typedef struct git_push git_push; typedef struct git_remote_head git_remote_head;+typedef struct git_remote_callbacks git_remote_callbacks; /** @} */ GIT_END_DECL
libgit2/include/git2/version.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,9 +7,9 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.17.0"+#define LIBGIT2_VERSION "0.18.0" #define LIBGIT2_VER_MAJOR 0-#define LIBGIT2_VER_MINOR 17+#define LIBGIT2_VER_MINOR 18 #define LIBGIT2_VER_REVISION 0 #endif
− libgit2/include/git2/windows.h
@@ -1,59 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */-#ifndef INCLUDE_git_windows_h__-#define INCLUDE_git_windows_h__--#include "common.h"--/**- * @file git2/windows.h- * @brief Windows-specific functions- * @ingroup Git- * @{- */-GIT_BEGIN_DECL--/**- * Set the active codepage for Windows syscalls- *- * All syscalls performed by the library will assume- * this codepage when converting paths and strings- * to use by the Windows kernel.- *- * The default value of UTF-8 will work automatically- * with most Git repositories created on Unix systems.- *- * This settings needs only be changed when working- * with repositories that contain paths in specific,- * non-UTF codepages.- *- * A full list of all available codepage identifiers may- * be found at:- *- * http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx- *- * @param codepage numeric codepage identifier- */-GIT_EXTERN(void) gitwin_set_codepage(unsigned int codepage);--/**- * Return the active codepage for Windows syscalls- *- * @return numeric codepage identifier- */-GIT_EXTERN(unsigned int) gitwin_get_codepage(void);--/**- * Set the active Windows codepage to UTF-8 (this is- * the default value)- */-GIT_EXTERN(void) gitwin_set_utf8(void);--/** @} */-GIT_END_DECL-#endif-
libgit2/src/attr.c view
@@ -1,10 +1,32 @@ #include "repository.h" #include "fileops.h" #include "config.h"+#include "attr.h"+#include "ignore.h"+#include "git2/oid.h" #include <ctype.h> GIT__USE_STRMAP; +const char *git_attr__true = "[internal]__TRUE__";+const char *git_attr__false = "[internal]__FALSE__";+const char *git_attr__unset = "[internal]__UNSET__";++git_attr_t git_attr_value(const char *attr)+{+ if (attr == NULL || attr == git_attr__unset)+ return GIT_ATTR_UNSPECIFIED_T;++ if (attr == git_attr__true)+ return GIT_ATTR_TRUE_T;++ if (attr == git_attr__false)+ return GIT_ATTR_FALSE_T;++ return GIT_ATTR_VALUE_T;+}++ static int collect_attr_files( git_repository *repo, uint32_t flags,@@ -22,7 +44,7 @@ int error; git_attr_path path; git_vector files = GIT_VECTOR_INIT;- unsigned int i, j;+ size_t i, j; git_attr_file *file; git_attr_name attr; git_attr_rule *rule;@@ -41,8 +63,9 @@ git_vector_foreach(&files, i, file) { git_attr_file__foreach_matching_rule(file, &path, j, rule) {- int pos = git_vector_bsearch(&rule->assigns, &attr);- if (pos >= 0) {+ size_t pos;++ if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) { *value = ((git_attr_assignment *)git_vector_get( &rule->assigns, pos))->value; goto cleanup;@@ -74,7 +97,7 @@ int error; git_attr_path path; git_vector files = GIT_VECTOR_INIT;- unsigned int i, j, k;+ size_t i, j, k; git_attr_file *file; git_attr_rule *rule; attr_get_many_info *info = NULL;@@ -96,7 +119,7 @@ git_attr_file__foreach_matching_rule(file, &path, j, rule) { for (k = 0; k < num_attr; k++) {- int pos;+ size_t pos; if (info[k].found != NULL) /* already found assignment */ continue;@@ -106,8 +129,7 @@ info[k].name.name_hash = git_attr_file__name_hash(names[k]); } - pos = git_vector_bsearch(&rule->assigns, &info[k].name);- if (pos >= 0) {+ if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) { info[k].found = (git_attr_assignment *) git_vector_get(&rule->assigns, pos); values[k] = info[k].found->value;@@ -138,7 +160,7 @@ int error; git_attr_path path; git_vector files = GIT_VECTOR_INIT;- unsigned int i, j, k;+ size_t i, j, k; git_attr_file *file; git_attr_rule *rule; git_attr_assignment *assign;@@ -163,11 +185,15 @@ continue; git_strmap_insert(seen, assign->name, assign, error);- if (error >= 0)- error = callback(assign->name, assign->value, payload);+ if (error < 0)+ goto cleanup; - if (error != 0)+ error = callback(assign->name, assign->value, payload);+ if (error) {+ giterr_clear();+ error = GIT_EUSER; goto cleanup;+ } } } }@@ -237,30 +263,30 @@ static int load_attr_file( const char **data,- git_attr_file_stat_sig *sig,+ git_futils_filestamp *stamp, const char *filename) { int error; git_buf content = GIT_BUF_INIT;- struct stat st; - if (p_stat(filename, &st) < 0)- return GIT_ENOTFOUND;+ error = git_futils_filestamp_check(stamp, filename);+ if (error < 0)+ return error; - if (sig != NULL &&- (git_time_t)st.st_mtime == sig->seconds &&- (git_off_t)st.st_size == sig->size &&- (unsigned int)st.st_ino == sig->ino)+ /* if error == 0, then file is up to date. By returning GIT_ENOTFOUND,+ * we tell the caller not to reparse this file...+ */+ if (!error) return GIT_ENOTFOUND; - error = git_futils_readbuffer_updated(&content, filename, NULL, NULL);- if (error < 0)- return error;+ error = git_futils_readbuffer(&content, filename);+ if (error < 0) {+ /* convert error into ENOTFOUND so failed permissions / invalid+ * file type don't actually stop the operation in progress.+ */+ return GIT_ENOTFOUND; - if (sig != NULL) {- sig->seconds = (git_time_t)st.st_mtime;- sig->size = (git_off_t)st.st_size;- sig->ino = (unsigned int)st.st_ino;+ /* TODO: once warnings are available, issue a warning callback */ } *data = git_buf_detach(&content);@@ -276,14 +302,15 @@ const char *relfile) { int error;+ size_t pos; git_index *index;- git_index_entry *entry;+ const git_index_entry *entry; if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||- (error = git_index_find(index, relfile)) < 0)+ (error = git_index_find(&pos, index, relfile)) < 0) return error; - entry = git_index_get(index, error);+ entry = git_index_get_byindex(index, pos); if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0) return GIT_ENOTFOUND;@@ -352,6 +379,7 @@ const char *filename, git_attr_file_source source, git_attr_file_parser parse,+ void* parsedata, git_vector *stack) { int error = 0;@@ -361,7 +389,7 @@ git_attr_cache *cache = git_repository_attr_cache(repo); git_attr_file *file = NULL; git_blob *blob = NULL;- git_attr_file_stat_sig st;+ git_futils_filestamp stamp; assert(filename && stack); @@ -383,12 +411,10 @@ /* if not in cache, load data, parse, and cache */ if (source == GIT_ATTR_FILE_FROM_FILE) {- if (file)- memcpy(&st, &file->cache_data.st, sizeof(st));- else- memset(&st, 0, sizeof(st));+ git_futils_filestamp_set(+ &stamp, file ? &file->cache_data.stamp : NULL); - error = load_attr_file(&content, &st, filename);+ error = load_attr_file(&content, &stamp, filename); } else { error = load_attr_blob_from_index(&content, &blob, repo, file ? &file->cache_data.oid : NULL, relfile);@@ -403,14 +429,19 @@ goto finish; } - if (!file &&- (error = git_attr_file__new(&file, source, relfile, &cache->pool)) < 0)- goto finish;+ /* if we got here, we have to parse and/or reparse the file */+ if (file)+ git_attr_file__clear_rules(file);+ else {+ error = git_attr_file__new(&file, source, relfile, &cache->pool);+ if (error < 0)+ goto finish;+ } - if (parse && (error = parse(repo, content, file)) < 0)+ if (parse && (error = parse(repo, parsedata, content, file)) < 0) goto finish; - git_strmap_insert(cache->files, file->key, file, error);+ git_strmap_insert(cache->files, file->key, file, error); //-V595 if (error > 0) error = 0; @@ -418,7 +449,7 @@ if (blob) git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob)); else- memcpy(&file->cache_data.st, &st, sizeof(st));+ git_futils_filestamp_set(&file->cache_data.stamp, &stamp); finish: /* push file onto vector if we found one*/@@ -439,7 +470,7 @@ } #define push_attr_file(R,S,B,F) \- git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,(S))+ git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,git_attr_file__parse_buffer,NULL,(S)) typedef struct { git_repository *repo;@@ -488,7 +519,7 @@ for (i = 0; !error && i < n_src; ++i) error = git_attr_cache__push_file( info->repo, path->ptr, GIT_ATTR_FILE, src[i],- git_attr_file__parse_buffer, info->files);+ git_attr_file__parse_buffer, NULL, info->files); return error; }@@ -550,8 +581,10 @@ error = git_futils_find_system_file(&dir, GIT_ATTR_FILE_SYSTEM); if (!error) error = push_attr_file(repo, files, NULL, dir.ptr);- else if (error == GIT_ENOTFOUND)+ else if (error == GIT_ENOTFOUND) {+ giterr_clear(); error = 0;+ } } cleanup:@@ -562,7 +595,30 @@ return error; } +static int attr_cache__lookup_path(+ const char **out, git_config *cfg, const char *key, const char *fallback)+{+ git_buf buf = GIT_BUF_INIT;+ int error; + if (!(error = git_config_get_string(out, cfg, key)))+ return 0;++ if (error == GIT_ENOTFOUND) {+ giterr_clear();+ error = 0;++ if (!git_futils_find_xdg_file(&buf, fallback))+ *out = git_buf_detach(&buf);+ else+ *out = NULL;++ git_buf_free(&buf);+ }++ return error;+}+ int git_attr_cache__init(git_repository *repo) { int ret;@@ -576,15 +632,15 @@ if (git_repository_config__weakptr(&cfg, repo) < 0) return -1; - ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG);- if (ret < 0 && ret != GIT_ENOTFOUND)+ ret = attr_cache__lookup_path(+ &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);+ if (ret < 0) return ret; - ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG);- if (ret < 0 && ret != GIT_ENOTFOUND)+ ret = attr_cache__lookup_path(+ &cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);+ if (ret < 0) return ret;-- giterr_clear(); /* allocate hashtable for attribute and ignore file contents */ if (cache->files == NULL) {
libgit2/src/attr.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,24 +8,12 @@ #define INCLUDE_attr_h__ #include "attr_file.h"-#include "strmap.h" -#define GIT_ATTR_CONFIG "core.attributesfile"-#define GIT_IGNORE_CONFIG "core.excludesfile"--typedef struct {- int initialized;- git_pool pool;- git_strmap *files; /* hash path to git_attr_file of rules */- git_strmap *macros; /* hash name to vector<git_attr_assignment> */- const char *cfg_attr_file; /* cached value of core.attributesfile */- const char *cfg_excl_file; /* cached value of core.excludesfile */-} git_attr_cache;+#define GIT_ATTR_CONFIG "core.attributesfile"+#define GIT_IGNORE_CONFIG "core.excludesfile" typedef int (*git_attr_file_parser)(- git_repository *, const char *, git_attr_file *);--extern int git_attr_cache__init(git_repository *repo);+ git_repository *, void *, const char *, git_attr_file *); extern int git_attr_cache__insert_macro( git_repository *repo, git_attr_rule *macro);@@ -39,6 +27,7 @@ const char *filename, git_attr_file_source source, git_attr_file_parser parse,+ void *parsedata, /* passed through to parse function */ git_vector *stack); extern int git_attr_cache__internal_file(
libgit2/src/attr_file.c view
@@ -1,16 +1,17 @@ #include "common.h" #include "repository.h" #include "filebuf.h"+#include "attr.h" #include "git2/blob.h" #include "git2/tree.h" #include <ctype.h> -const char *git_attr__true = "[internal]__TRUE__";-const char *git_attr__false = "[internal]__FALSE__";-const char *git_attr__unset = "[internal]__UNSET__";- static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); static void git_attr_rule__clear(git_attr_rule *rule);+static bool parse_optimized_patterns(+ git_attr_fnmatch *spec,+ git_pool *pool,+ const char *pattern); int git_attr_file__new( git_attr_file **attrs_ptr,@@ -57,13 +58,15 @@ } int git_attr_file__parse_buffer(- git_repository *repo, const char *buffer, git_attr_file *attrs)+ git_repository *repo, void *parsedata, const char *buffer, git_attr_file *attrs) { int error = 0; const char *scan = NULL; char *context = NULL; git_attr_rule *rule = NULL; + GIT_UNUSED(parsedata);+ assert(buffer && attrs); scan = buffer;@@ -127,7 +130,7 @@ if (!(error = git_futils_readbuffer(&content, path))) error = git_attr_file__parse_buffer(- NULL, git_buf_cstr(&content), *attrs_ptr);+ NULL, NULL, git_buf_cstr(&content), *attrs_ptr); git_buf_free(&content); @@ -139,19 +142,24 @@ return error; } -void git_attr_file__free(git_attr_file *file)+void git_attr_file__clear_rules(git_attr_file *file) { unsigned int i; git_attr_rule *rule; - if (!file)- return;- git_vector_foreach(&file->rules, i, rule) git_attr_rule__free(rule); git_vector_free(&file->rules);+} +void git_attr_file__free(git_attr_file *file)+{+ if (!file)+ return;++ git_attr_file__clear_rules(file);+ if (file->pool_is_allocated) { git_pool_clear(file->pool); git__free(file->pool);@@ -178,7 +186,7 @@ const char *attr, const char **value) {- unsigned int i;+ size_t i; git_attr_name name; git_attr_rule *rule; @@ -188,9 +196,9 @@ name.name_hash = git_attr_file__name_hash(attr); git_attr_file__foreach_matching_rule(file, path, i, rule) {- int pos = git_vector_bsearch(&rule->assigns, &name);+ size_t pos; - if (pos >= 0) {+ if (!git_vector_bsearch(&pos, &rule->assigns, &name)) { *value = ((git_attr_assignment *) git_vector_get(&rule->assigns, pos))->value; break;@@ -206,16 +214,17 @@ const git_attr_path *path) { int fnm;+ int icase_flags = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? FNM_CASEFOLD : 0; if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY && !path->is_dir) return false; if (match->flags & GIT_ATTR_FNMATCH_FULLPATH)- fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME);+ fnm = p_fnmatch(match->pattern, path->path, FNM_PATHNAME | icase_flags); else if (path->is_dir)- fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR);+ fnm = p_fnmatch(match->pattern, path->basename, FNM_LEADING_DIR | icase_flags); else- fnm = p_fnmatch(match->pattern, path->basename, 0);+ fnm = p_fnmatch(match->pattern, path->basename, icase_flags); return (fnm == FNM_NOMATCH) ? false : true; }@@ -236,32 +245,30 @@ git_attr_assignment *git_attr_rule__lookup_assignment( git_attr_rule *rule, const char *name) {- int pos;+ size_t pos; git_attr_name key; key.name = name; key.name_hash = git_attr_file__name_hash(name); - pos = git_vector_bsearch(&rule->assigns, &key);+ if (git_vector_bsearch(&pos, &rule->assigns, &key))+ return NULL; - return (pos >= 0) ? git_vector_get(&rule->assigns, pos) : NULL;+ return git_vector_get(&rule->assigns, pos); } int git_attr_path__init( git_attr_path *info, const char *path, const char *base) {+ ssize_t root;+ /* build full path as best we can */ git_buf_init(&info->full, 0); - if (base != NULL && git_path_root(path) < 0) {- if (git_buf_joinpath(&info->full, base, path) < 0)- return -1;- info->path = info->full.ptr + strlen(base);- } else {- if (git_buf_sets(&info->full, path) < 0)- return -1;- info->path = info->full.ptr;- }+ if (git_path_join_unrooted(&info->full, path, base, &root) < 0)+ return -1; + info->path = info->full.ptr + root;+ /* remove trailing slashes */ while (info->full.size > 0) { if (info->full.ptr[info->full.size - 1] != '/')@@ -293,7 +300,6 @@ info->basename = NULL; } - /* * From gitattributes(5): *@@ -338,10 +344,16 @@ const char **base) { const char *pattern, *scan;- int slash_count;+ int slash_count, allow_space; assert(spec && base && *base); + if (parse_optimized_patterns(spec, pool, *base))+ return 0;++ spec->flags = (spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE);+ allow_space = (spec->flags != 0);+ pattern = *base; while (git__isspace(*pattern)) pattern++;@@ -350,8 +362,6 @@ return GIT_ENOTFOUND; } - spec->flags = 0;- if (*pattern == '[') { if (strncmp(pattern, "[attr]", 6) == 0) { spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;@@ -368,8 +378,10 @@ slash_count = 0; for (scan = pattern; *scan != '\0'; ++scan) { /* scan until (non-escaped) white space */- if (git__isspace(*scan) && *(scan - 1) != '\\')- break;+ if (git__isspace(*scan) && *(scan - 1) != '\\') {+ if (!allow_space || (*scan != ' ' && *scan != '\t'))+ break;+ } if (*scan == '/') { spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;@@ -418,20 +430,26 @@ return -1; } else { /* strip '\' that might have be used for internal whitespace */- char *to = spec->pattern;- for (scan = spec->pattern; *scan; to++, scan++) {- if (*scan == '\\')- scan++; /* skip '\' but include next char */- if (to != scan)- *to = *scan;- }- if (to != scan) {- *to = '\0';- spec->length = (to - spec->pattern);- }+ spec->length = git__unescape(spec->pattern); } return 0;+}++static bool parse_optimized_patterns(+ git_attr_fnmatch *spec,+ git_pool *pool,+ const char *pattern)+{+ if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {+ spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;+ spec->pattern = git_pool_strndup(pool, pattern, 1);+ spec->length = 1;++ return true;+ }++ return false; } static int sort_by_hash_and_name(const void *a_raw, const void *b_raw)
libgit2/src/attr_file.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,14 +7,17 @@ #ifndef INCLUDE_attr_file_h__ #define INCLUDE_attr_file_h__ +#include "git2/oid.h" #include "git2/attr.h" #include "vector.h" #include "pool.h" #include "buffer.h"+#include "fileops.h" #define GIT_ATTR_FILE ".gitattributes" #define GIT_ATTR_FILE_INREPO "info/attributes" #define GIT_ATTR_FILE_SYSTEM "gitattributes"+#define GIT_ATTR_FILE_XDG "attributes" #define GIT_ATTR_FNMATCH_NEGATIVE (1U << 0) #define GIT_ATTR_FNMATCH_DIRECTORY (1U << 1)@@ -22,7 +25,14 @@ #define GIT_ATTR_FNMATCH_MACRO (1U << 3) #define GIT_ATTR_FNMATCH_IGNORE (1U << 4) #define GIT_ATTR_FNMATCH_HASWILD (1U << 5)+#define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6)+#define GIT_ATTR_FNMATCH_ICASE (1U << 7)+#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) +extern const char *git_attr__true;+extern const char *git_attr__false;+extern const char *git_attr__unset;+ typedef struct { char *pattern; size_t length;@@ -48,27 +58,21 @@ } git_attr_assignment; typedef struct {- git_time_t seconds;- git_off_t size;- unsigned int ino;-} git_attr_file_stat_sig;--typedef struct { char *key; /* cache "source#path" this was loaded from */ git_vector rules; /* vector of <rule*> or <fnmatch*> */ git_pool *pool; bool pool_is_allocated; union { git_oid oid;- git_attr_file_stat_sig st;+ git_futils_filestamp stamp; } cache_data; } git_attr_file; typedef struct {- git_buf full;- const char *path;- const char *basename;- int is_dir;+ git_buf full;+ char *path;+ char *basename;+ int is_dir; } git_attr_path; typedef enum {@@ -88,8 +92,10 @@ extern void git_attr_file__free(git_attr_file *file); +extern void git_attr_file__clear_rules(git_attr_file *file);+ extern int git_attr_file__parse_buffer(- git_repository *repo, const char *buf, git_attr_file *file);+ git_repository *repo, void *parsedata, const char *buf, git_attr_file *file); extern int git_attr_file__lookup_one( git_attr_file *file,
+ libgit2/src/attrcache.h view
@@ -0,0 +1,24 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_attrcache_h__+#define INCLUDE_attrcache_h__++#include "pool.h"+#include "strmap.h"++typedef struct {+ int initialized;+ git_pool pool;+ git_strmap *files; /* hash path to git_attr_file of rules */+ git_strmap *macros; /* hash name to vector<git_attr_assignment> */+ const char *cfg_attr_file; /* cached value of core.attributesfile */+ const char *cfg_excl_file; /* cached value of core.excludesfile */+} git_attr_cache;++extern int git_attr_cache__init(git_repository *repo);++#endif
libgit2/src/blob.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -12,17 +12,18 @@ #include "common.h" #include "blob.h" #include "filter.h"+#include "buf_text.h" -const void *git_blob_rawcontent(git_blob *blob)+const void *git_blob_rawcontent(const git_blob *blob) { assert(blob); return blob->odb_object->raw.data; } -size_t git_blob_rawsize(git_blob *blob)+git_off_t git_blob_rawsize(const git_blob *blob) { assert(blob);- return blob->odb_object->raw.len;+ return (git_off_t)blob->odb_object->raw.len; } int git_blob__getbuf(git_buf *buffer, git_blob *blob)@@ -68,6 +69,7 @@ int fd, error; char buffer[4096]; git_odb_stream *stream = NULL;+ ssize_t read_len = -1, written = 0; if ((error = git_odb_open_wstream( &stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0)@@ -78,20 +80,18 @@ return -1; } - while (!error && file_size > 0) {- ssize_t read_len = p_read(fd, buffer, sizeof(buffer));-- if (read_len < 0) {- giterr_set(- GITERR_OS, "Failed to create blob. Can't read whole file");- error = -1;- }- else if (!(error = stream->write(stream, buffer, read_len)))- file_size -= read_len;+ while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {+ error = stream->write(stream, buffer, read_len);+ written += read_len; } p_close(fd); + if (written != file_size || read_len < 0) {+ giterr_set(GITERR_OS, "Failed to read file into stream");+ error = -1;+ }+ if (!error) error = stream->finalize_write(oid, stream); @@ -148,27 +148,31 @@ return error; } -static int blob_create_internal(git_oid *oid, git_repository *repo, const char *path)+static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters) { int error; struct stat st; git_odb *odb = NULL; git_off_t size; - if ((error = git_path_lstat(path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0)+ assert(hint_path || !try_load_filters);++ if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0) return error; size = st.st_size; if (S_ISLNK(st.st_mode)) {- error = write_symlink(oid, odb, path, (size_t)size);+ error = write_symlink(oid, odb, content_path, (size_t)size); } else { git_vector write_filters = GIT_VECTOR_INIT;- int filter_count;+ int filter_count = 0; - /* Load the filters for writing this file to the ODB */- filter_count = git_filters_load(- &write_filters, repo, path, GIT_FILTER_TO_ODB);+ if (try_load_filters) {+ /* Load the filters for writing this file to the ODB */+ filter_count = git_filters_load(+ &write_filters, repo, hint_path, GIT_FILTER_TO_ODB);+ } if (filter_count < 0) { /* Negative value means there was a critical error */@@ -176,10 +180,10 @@ } else if (filter_count == 0) { /* No filters need to be applied to the document: we can stream * directly from disk */- error = write_file_stream(oid, odb, path, size);+ error = write_file_stream(oid, odb, content_path, size); } else { /* We need to apply one or more filters */- error = write_file_filtered(oid, odb, path, &write_filters);+ error = write_file_filtered(oid, odb, content_path, &write_filters); } git_filters_free(&write_filters);@@ -202,21 +206,25 @@ return error; } -int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *path)+int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char *path) { git_buf full_path = GIT_BUF_INIT; const char *workdir; int error; + if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0)+ return error;+ workdir = git_repository_workdir(repo);- assert(workdir); /* error to call this on bare repo */ if (git_buf_joinpath(&full_path, workdir, path) < 0) { git_buf_free(&full_path); return -1; } - error = blob_create_internal(oid, repo, git_buf_cstr(&full_path));+ error = blob_create_internal(+ oid, repo, git_buf_cstr(&full_path),+ git_buf_cstr(&full_path) + strlen(workdir), true); git_buf_free(&full_path); return error;@@ -226,14 +234,88 @@ { int error; git_buf full_path = GIT_BUF_INIT;+ const char *workdir, *hintpath; if ((error = git_path_prettify(&full_path, path, NULL)) < 0) { git_buf_free(&full_path); return error; } - error = blob_create_internal(oid, repo, git_buf_cstr(&full_path));+ hintpath = git_buf_cstr(&full_path);+ workdir = git_repository_workdir(repo); + if (workdir && !git__prefixcmp(hintpath, workdir))+ hintpath += strlen(workdir);++ error = blob_create_internal(+ oid, repo, git_buf_cstr(&full_path), hintpath, true);+ git_buf_free(&full_path); return error;+}++#define BUFFER_SIZE 4096++int git_blob_create_fromchunks(+ git_oid *oid,+ git_repository *repo,+ const char *hintpath,+ int (*source_cb)(char *content, size_t max_length, void *payload),+ void *payload)+{+ int error = -1, read_bytes;+ char *content = NULL;+ git_filebuf file = GIT_FILEBUF_INIT;+ git_buf path = GIT_BUF_INIT;++ if (git_buf_join_n(+ &path, '/', 3, + git_repository_path(repo),+ GIT_OBJECTS_DIR, + "streamed") < 0)+ goto cleanup;++ content = git__malloc(BUFFER_SIZE);+ GITERR_CHECK_ALLOC(content);++ if (git_filebuf_open(&file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY) < 0)+ goto cleanup;++ while (1) {+ read_bytes = source_cb(content, BUFFER_SIZE, payload);++ assert(read_bytes <= BUFFER_SIZE);++ if (read_bytes <= 0)+ break;++ if (git_filebuf_write(&file, content, read_bytes) < 0)+ goto cleanup;+ }++ if (read_bytes < 0)+ goto cleanup;++ if (git_filebuf_flush(&file) < 0)+ goto cleanup;++ error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL);++cleanup:+ git_buf_free(&path);+ git_filebuf_cleanup(&file);+ git__free(content);+ return error;+}++int git_blob_is_binary(git_blob *blob)+{+ git_buf content;++ assert(blob);++ content.ptr = blob->odb_object->raw.data;+ content.size = min(blob->odb_object->raw.len, 4000);++ return git_buf_text_is_binary(&content); }
libgit2/src/blob.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/branch.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,9 +7,13 @@ #include "common.h" #include "commit.h"-#include "branch.h" #include "tag.h"+#include "config.h"+#include "refspec.h"+#include "refs.h" +#include "git2/branch.h"+ static int retrieve_branch_reference( git_reference **branch_reference_out, git_repository *repo,@@ -41,168 +45,539 @@ return error; } -static int create_error_invalid(const char *msg)+static int not_a_local_branch(const char *reference_name) {- giterr_set(GITERR_INVALID, "Cannot create branch - %s", msg);+ giterr_set(+ GITERR_INVALID,+ "Reference '%s' is not a local branch.", reference_name); return -1; } int git_branch_create(- git_oid *oid_out,- git_repository *repo,- const char *branch_name,- const git_object *target,- int force)+ git_reference **ref_out,+ git_repository *repository,+ const char *branch_name,+ const git_commit *commit,+ int force) {- git_otype target_type = GIT_OBJ_BAD;- git_object *commit = NULL; git_reference *branch = NULL; git_buf canonical_branch_name = GIT_BUF_INIT; int error = -1; - assert(repo && branch_name && target && oid_out);-- if (git_object_owner(target) != repo)- return create_error_invalid("The given target does not belong to this repository");-- target_type = git_object_type(target);-- switch (target_type)- {- case GIT_OBJ_TAG:- if (git_tag_peel(&commit, (git_tag *)target) < 0)- goto cleanup;-- if (git_object_type(commit) != GIT_OBJ_COMMIT) {- create_error_invalid("The given target does not resolve to a commit");- goto cleanup;- }- break;-- case GIT_OBJ_COMMIT:- commit = (git_object *)target;- break;-- default:- return create_error_invalid("Only git_tag and git_commit objects are valid targets.");- }+ assert(branch_name && commit && ref_out);+ assert(git_object_owner((const git_object *)commit) == repository); if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) goto cleanup; - if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0)- goto cleanup;+ error = git_reference_create(&branch, repository,+ git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force); - git_oid_cpy(oid_out, git_reference_oid(branch));- error = 0;+ if (!error)+ *ref_out = branch; cleanup:- if (target_type == GIT_OBJ_TAG)- git_object_free(commit);-- git_reference_free(branch); git_buf_free(&canonical_branch_name); return error; } -int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_t branch_type)+int git_branch_delete(git_reference *branch) {- git_reference *branch = NULL;- git_reference *head = NULL;- int error;+ int is_head;+ git_buf config_section = GIT_BUF_INIT;+ int error = -1; - assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE));+ assert(branch); - if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0)- return error;+ if (!git_reference_is_branch(branch) &&+ !git_reference_is_remote(branch)) {+ giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.", git_reference_name(branch));+ return -1;+ } - if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {- giterr_set(GITERR_REFERENCE, "Cannot locate HEAD.");- goto on_error;+ if ((is_head = git_branch_is_head(branch)) < 0)+ return is_head;++ if (is_head) {+ giterr_set(GITERR_REFERENCE,+ "Cannot delete branch '%s' as it is the current HEAD of the repository.", git_reference_name(branch));+ return -1; } - if ((git_reference_type(head) == GIT_REF_SYMBOLIC)- && (strcmp(git_reference_target(head), git_reference_name(branch)) == 0)) {- giterr_set(GITERR_REFERENCE,- "Cannot delete branch '%s' as it is the current HEAD of the repository.", branch_name);+ if (git_buf_printf(&config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)+ goto on_error;++ if (git_config_rename_section(+ git_reference_owner(branch), + git_buf_cstr(&config_section),+ NULL) < 0) goto on_error;- } if (git_reference_delete(branch) < 0) goto on_error; - git_reference_free(head);- return 0;+ error = 0; on_error:- git_reference_free(head);- git_reference_free(branch);- return -1;+ git_buf_free(&config_section);+ return error; } typedef struct {- git_vector *branchlist;+ git_branch_foreach_cb branch_cb;+ void *callback_payload; unsigned int branch_type;-} branch_filter_data;+} branch_foreach_filter; -static int branch_list_cb(const char *branch_name, void *payload)+static int branch_foreach_cb(const char *branch_name, void *payload) {- branch_filter_data *filter = (branch_filter_data *)payload;+ branch_foreach_filter *filter = (branch_foreach_filter *)payload; - if ((filter->branch_type & GIT_BRANCH_LOCAL && git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)- || (filter->branch_type & GIT_BRANCH_REMOTE && git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0))- return git_vector_insert(filter->branchlist, git__strdup(branch_name));+ if (filter->branch_type & GIT_BRANCH_LOCAL &&+ git__prefixcmp(branch_name, GIT_REFS_HEADS_DIR) == 0)+ return filter->branch_cb(branch_name + strlen(GIT_REFS_HEADS_DIR), GIT_BRANCH_LOCAL, filter->callback_payload); + if (filter->branch_type & GIT_BRANCH_REMOTE &&+ git__prefixcmp(branch_name, GIT_REFS_REMOTES_DIR) == 0)+ return filter->branch_cb(branch_name + strlen(GIT_REFS_REMOTES_DIR), GIT_BRANCH_REMOTE, filter->callback_payload);+ return 0; } -int git_branch_list(git_strarray *branch_names, git_repository *repo, unsigned int list_flags)+int git_branch_foreach(+ git_repository *repo,+ unsigned int list_flags,+ git_branch_foreach_cb branch_cb,+ void *payload) {+ branch_foreach_filter filter;++ filter.branch_cb = branch_cb;+ filter.branch_type = list_flags;+ filter.callback_payload = payload;++ return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter);+}++int git_branch_move(+ git_reference **out,+ git_reference *branch,+ const char *new_branch_name,+ int force)+{+ git_buf new_reference_name = GIT_BUF_INIT,+ old_config_section = GIT_BUF_INIT,+ new_config_section = GIT_BUF_INIT; int error;- branch_filter_data filter;- git_vector branchlist; - assert(branch_names && repo);+ assert(branch && new_branch_name); - if (git_vector_init(&branchlist, 8, NULL) < 0)+ if (!git_reference_is_branch(branch))+ return not_a_local_branch(git_reference_name(branch));++ if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0 ||+ (error = git_buf_printf(&old_config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR))) < 0 ||+ (error = git_buf_printf(&new_config_section, "branch.%s", new_branch_name)) < 0)+ goto done;++ if ((error = git_config_rename_section(git_reference_owner(branch),+ git_buf_cstr(&old_config_section),+ git_buf_cstr(&new_config_section))) < 0)+ goto done;+ + if ((error = git_reference_rename(out, branch, git_buf_cstr(&new_reference_name), force)) < 0)+ goto done;++done:+ git_buf_free(&new_reference_name);+ git_buf_free(&old_config_section);+ git_buf_free(&new_config_section);++ return error;+}++int git_branch_lookup(+ git_reference **ref_out,+ git_repository *repo,+ const char *branch_name,+ git_branch_t branch_type)+{+ assert(ref_out && repo && branch_name);++ return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);+}++int git_branch_name(const char **out, git_reference *ref)+{+ const char *branch_name;++ assert(out && ref);++ branch_name = ref->name;++ if (git_reference_is_branch(ref)) {+ branch_name += strlen(GIT_REFS_HEADS_DIR);+ } else if (git_reference_is_remote(ref)) {+ branch_name += strlen(GIT_REFS_REMOTES_DIR);+ } else {+ giterr_set(GITERR_INVALID,+ "Reference '%s' is neither a local nor a remote branch.", ref->name); return -1;+ }+ *out = branch_name;+ return 0;+} - filter.branchlist = &branchlist;- filter.branch_type = list_flags;+static int retrieve_upstream_configuration(+ const char **out,+ git_repository *repo,+ const char *canonical_branch_name,+ const char *format)+{+ git_config *config;+ git_buf buf = GIT_BUF_INIT;+ int error; - error = git_reference_foreach(repo, GIT_REF_LISTALL, &branch_list_cb, (void *)&filter);- if (error < 0) {- git_vector_free(&branchlist);+ if (git_repository_config__weakptr(&config, repo) < 0) return -1;++ if (git_buf_printf(&buf, format,+ canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)+ return -1;++ error = git_config_get_string(out, config, git_buf_cstr(&buf));+ git_buf_free(&buf);+ return error;+}++int git_branch_upstream__name(+ git_buf *tracking_name,+ git_repository *repo,+ const char *canonical_branch_name)+{+ const char *remote_name, *merge_name;+ git_buf buf = GIT_BUF_INIT;+ int error = -1;+ git_remote *remote = NULL;+ const git_refspec *refspec;++ assert(tracking_name && canonical_branch_name);++ if (!git_reference__is_branch(canonical_branch_name))+ return not_a_local_branch(canonical_branch_name);++ if ((error = retrieve_upstream_configuration(+ &remote_name, repo, canonical_branch_name, "branch.%s.remote")) < 0)+ goto cleanup;++ if ((error = retrieve_upstream_configuration(+ &merge_name, repo, canonical_branch_name, "branch.%s.merge")) < 0)+ goto cleanup;++ if (!*remote_name || !*merge_name) {+ error = GIT_ENOTFOUND;+ goto cleanup; } - branch_names->strings = (char **)branchlist.contents;- branch_names->count = branchlist.length;- return 0;+ if (strcmp(".", remote_name) != 0) {+ if ((error = git_remote_load(&remote, repo, remote_name)) < 0)+ goto cleanup;++ refspec = git_remote_fetchspec(remote);+ if (refspec == NULL+ || refspec->src == NULL+ || refspec->dst == NULL) {+ error = GIT_ENOTFOUND;+ goto cleanup;+ }++ if (git_refspec_transform_r(&buf, refspec, merge_name) < 0)+ goto cleanup;+ } else+ if (git_buf_sets(&buf, merge_name) < 0)+ goto cleanup;++ error = git_buf_set(tracking_name, git_buf_cstr(&buf), git_buf_len(&buf));++cleanup:+ git_remote_free(remote);+ git_buf_free(&buf);+ return error; } -int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force)+static int remote_name(git_buf *buf, git_repository *repo, const char *canonical_branch_name) {- git_reference *reference = NULL;- git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT;+ git_strarray remote_list = {0};+ size_t i;+ git_remote *remote;+ const git_refspec *fetchspec; int error = 0;+ char *remote_name = NULL; - if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0)+ assert(buf && repo && canonical_branch_name);++ /* Verify that this is a remote branch */+ if (!git_reference__is_remote(canonical_branch_name)) {+ giterr_set(GITERR_INVALID, "Reference '%s' is not a remote branch.",+ canonical_branch_name);+ error = GIT_ERROR; goto cleanup;+ } - /* We need to be able to return GIT_ENOTFOUND */- if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0)+ /* Get the remotes */+ if ((error = git_remote_list(&remote_list, repo)) < 0) goto cleanup; - if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)+ /* Find matching remotes */+ for (i = 0; i < remote_list.count; i++) {+ if ((error = git_remote_load(&remote, repo, remote_list.strings[i])) < 0)+ continue;++ fetchspec = git_remote_fetchspec(remote);++ /* Defensivly check that we have a fetchspec */+ if (fetchspec &&+ git_refspec_dst_matches(fetchspec, canonical_branch_name)) {+ /* If we have not already set out yet, then set+ * it to the matching remote name. Otherwise+ * multiple remotes match this reference, and it+ * is ambiguous. */+ if (!remote_name) {+ remote_name = remote_list.strings[i];+ } else {+ git_remote_free(remote);+ error = GIT_EAMBIGUOUS;+ goto cleanup;+ }+ }++ git_remote_free(remote);+ }++ if (remote_name) {+ git_buf_clear(buf);+ error = git_buf_puts(buf, remote_name);+ } else {+ error = GIT_ENOTFOUND;+ }++cleanup:+ git_strarray_free(&remote_list);+ return error;+}++int git_branch_remote_name(char *buffer, size_t buffer_len, git_repository *repo, const char *refname)+{+ int ret;+ git_buf buf = GIT_BUF_INIT;++ if ((ret = remote_name(&buf, repo, refname)) < 0)+ return ret;++ if (buffer)+ git_buf_copy_cstr(buffer, buffer_len, &buf);++ ret = git_buf_len(&buf) + 1;+ git_buf_free(&buf);++ return ret;+}++int git_branch_upstream_name(+ char *tracking_branch_name_out,+ size_t buffer_size,+ git_repository *repo,+ const char *canonical_branch_name)+{+ git_buf buf = GIT_BUF_INIT;+ int error;++ assert(canonical_branch_name);++ if (tracking_branch_name_out && buffer_size)+ *tracking_branch_name_out = '\0';++ if ((error = git_branch_upstream__name(+ &buf, repo, canonical_branch_name)) < 0)+ goto cleanup;++ if (tracking_branch_name_out && buf.size + 1 > buffer_size) { /* +1 for NUL byte */+ giterr_set(+ GITERR_INVALID,+ "Buffer too short to hold the tracked reference name.");+ error = -1; goto cleanup;+ } - error = git_reference_rename(reference, git_buf_cstr(&new_reference_name), force);+ if (tracking_branch_name_out)+ git_buf_copy_cstr(tracking_branch_name_out, buffer_size, &buf); + error = (int)buf.size + 1;+ cleanup:- git_reference_free(reference);- git_buf_free(&old_reference_name);- git_buf_free(&new_reference_name);+ git_buf_free(&buf);+ return (int)error;+} +int git_branch_upstream(+ git_reference **tracking_out,+ git_reference *branch)+{+ int error;+ git_buf tracking_name = GIT_BUF_INIT;++ if ((error = git_branch_upstream__name(&tracking_name,+ git_reference_owner(branch), git_reference_name(branch))) < 0)+ return error;++ error = git_reference_lookup(+ tracking_out,+ git_reference_owner(branch),+ git_buf_cstr(&tracking_name));++ git_buf_free(&tracking_name); return error;+}++static int unset_upstream(git_config *config, const char *shortname)+{+ git_buf buf = GIT_BUF_INIT;++ if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0)+ return -1;++ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)+ goto on_error;++ git_buf_clear(&buf);+ if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0)+ goto on_error;++ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)+ goto on_error;++ git_buf_free(&buf);+ return 0;++on_error:+ git_buf_free(&buf);+ return -1;+}++int git_branch_set_upstream(git_reference *branch, const char *upstream_name)+{+ git_buf key = GIT_BUF_INIT, value = GIT_BUF_INIT;+ git_reference *upstream;+ git_repository *repo;+ git_remote *remote = NULL;+ git_config *config;+ const char *name, *shortname;+ int local;+ const git_refspec *fetchspec;++ name = git_reference_name(branch);+ if (!git_reference__is_branch(name))+ return not_a_local_branch(name);++ if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)+ return -1;++ shortname = name + strlen(GIT_REFS_HEADS_DIR);++ if (upstream_name == NULL)+ return unset_upstream(config, shortname);++ repo = git_reference_owner(branch);++ /* First we need to figure out whether it's a branch or remote-tracking */+ if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_LOCAL) == 0)+ local = 1;+ else if (git_branch_lookup(&upstream, repo, upstream_name, GIT_BRANCH_REMOTE) == 0)+ local = 0;+ else+ return GIT_ENOTFOUND;++ /*+ * If it's local, the remote is "." and the branch name is+ * simply the refname. Otherwise we need to figure out what+ * the remote-tracking branch's name on the remote is and use+ * that.+ */+ if (local)+ git_buf_puts(&value, ".");+ else+ remote_name(&value, repo, git_reference_name(upstream));++ if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0)+ goto on_error;++ if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)+ goto on_error;++ if (local) {+ if (git_buf_puts(&value, git_reference_name(branch)) < 0)+ goto on_error;+ } else {+ /* Get the remoe-tracking branch's refname in its repo */+ if (git_remote_load(&remote, repo, git_buf_cstr(&value)) < 0)+ goto on_error;++ fetchspec = git_remote_fetchspec(remote);+ git_buf_clear(&value);+ if (git_refspec_transform_l(&value, fetchspec, git_reference_name(upstream)) < 0)+ goto on_error;++ git_remote_free(remote);+ remote = NULL;+ }++ git_buf_clear(&key);+ if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0)+ goto on_error;++ if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&value)) < 0)+ goto on_error;++ git_reference_free(upstream);+ git_buf_free(&key);+ git_buf_free(&value);++ return 0;++on_error:+ git_reference_free(upstream);+ git_buf_free(&key);+ git_buf_free(&value);+ git_remote_free(remote);++ return -1;+}++int git_branch_is_head(+ git_reference *branch)+{+ git_reference *head;+ bool is_same = false;+ int error;++ assert(branch);++ if (!git_reference_is_branch(branch))+ return false;++ error = git_repository_head(&head, git_reference_owner(branch));++ if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)+ return false;++ if (error < 0)+ return -1;++ is_same = strcmp(+ git_reference_name(branch),+ git_reference_name(head)) == 0;++ git_reference_free(head);++ return is_same; }
libgit2/src/branch.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,11 +7,11 @@ #ifndef INCLUDE_branch_h__ #define INCLUDE_branch_h__ -#include "git2/branch.h"+#include "buffer.h" -struct git_branch {- char *remote; /* TODO: Make this a git_remote */- char *merge;-};+int git_branch_upstream__name(+ git_buf *tracking_name,+ git_repository *repo,+ const char *canonical_branch_name); #endif
libgit2/src/bswap.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
+ libgit2/src/buf_text.c view
@@ -0,0 +1,291 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#include "buf_text.h"++int git_buf_text_puts_escaped(+ git_buf *buf,+ const char *string,+ const char *esc_chars,+ const char *esc_with)+{+ const char *scan;+ size_t total = 0, esc_len = strlen(esc_with), count;++ if (!string)+ return 0;++ for (scan = string; *scan; ) {+ /* count run of non-escaped characters */+ count = strcspn(scan, esc_chars);+ total += count;+ scan += count;+ /* count run of escaped characters */+ count = strspn(scan, esc_chars);+ total += count * (esc_len + 1);+ scan += count;+ }++ if (git_buf_grow(buf, buf->size + total + 1) < 0)+ return -1;++ for (scan = string; *scan; ) {+ count = strcspn(scan, esc_chars);++ memmove(buf->ptr + buf->size, scan, count);+ scan += count;+ buf->size += count;++ for (count = strspn(scan, esc_chars); count > 0; --count) {+ /* copy escape sequence */+ memmove(buf->ptr + buf->size, esc_with, esc_len);+ buf->size += esc_len;+ /* copy character to be escaped */+ buf->ptr[buf->size] = *scan;+ buf->size++;+ scan++;+ }+ }++ buf->ptr[buf->size] = '\0';++ return 0;+}++void git_buf_text_unescape(git_buf *buf)+{+ buf->size = git__unescape(buf->ptr);+}++int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)+{+ const char *scan = src->ptr;+ const char *scan_end = src->ptr + src->size;+ const char *next = memchr(scan, '\r', src->size);+ char *out;++ assert(tgt != src);++ if (!next)+ return GIT_ENOTFOUND;++ /* reduce reallocs while in the loop */+ if (git_buf_grow(tgt, src->size) < 0)+ return -1;+ out = tgt->ptr;+ tgt->size = 0;++ /* Find the next \r and copy whole chunk up to there to tgt */+ for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {+ if (next > scan) {+ size_t copylen = next - scan;+ memcpy(out, scan, copylen);+ out += copylen;+ }++ /* Do not drop \r unless it is followed by \n */+ if (next[1] != '\n')+ *out++ = '\r';+ }++ /* Copy remaining input into dest */+ memcpy(out, scan, scan_end - scan + 1); /* +1 for NUL byte */+ out += (scan_end - scan);+ tgt->size = out - tgt->ptr;++ return 0;+}++int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)+{+ const char *start = src->ptr;+ const char *end = start + src->size;+ const char *scan = start;+ const char *next = memchr(scan, '\n', src->size);++ assert(tgt != src);++ if (!next)+ return GIT_ENOTFOUND;++ /* attempt to reduce reallocs while in the loop */+ if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0)+ return -1;+ tgt->size = 0;++ for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {+ size_t copylen = next - scan;+ /* don't convert existing \r\n to \r\r\n */+ size_t extralen = (next > start && next[-1] == '\r') ? 1 : 2;+ size_t needsize = tgt->size + copylen + extralen + 1;++ if (tgt->asize < needsize && git_buf_grow(tgt, needsize) < 0)+ return -1;++ if (next > scan) {+ memcpy(tgt->ptr + tgt->size, scan, copylen);+ tgt->size += copylen;+ }+ if (extralen == 2)+ tgt->ptr[tgt->size++] = '\r';+ tgt->ptr[tgt->size++] = '\n';+ }++ return git_buf_put(tgt, scan, end - scan);+}++int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings)+{+ size_t i;+ const char *str, *pfx;++ git_buf_clear(buf);++ if (!strings || !strings->count)+ return 0;++ /* initialize common prefix to first string */+ if (git_buf_sets(buf, strings->strings[0]) < 0)+ return -1;++ /* go through the rest of the strings, truncating to shared prefix */+ for (i = 1; i < strings->count; ++i) {++ for (str = strings->strings[i], pfx = buf->ptr;+ *str && *str == *pfx; str++, pfx++)+ /* scanning */;++ git_buf_truncate(buf, pfx - buf->ptr);++ if (!buf->size)+ break;+ }++ return 0;+}++bool git_buf_text_is_binary(const git_buf *buf)+{+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;+ int printable = 0, nonprintable = 0;++ while (scan < end) {+ unsigned char c = *scan++;++ if (c > 0x1F && c < 0x7F)+ printable++;+ else if (c == '\0')+ return true;+ else if (!git__isspace(c))+ nonprintable++;+ }++ return ((printable >> 7) < nonprintable);+}++bool git_buf_text_contains_nul(const git_buf *buf)+{+ return (memchr(buf->ptr, '\0', buf->size) != NULL);+}++int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf, size_t offset)+{+ const char *ptr;+ size_t len;++ *bom = GIT_BOM_NONE;+ /* need at least 2 bytes after offset to look for any BOM */+ if (buf->size < offset + 2)+ return 0;++ ptr = buf->ptr + offset;+ len = buf->size - offset;++ switch (*ptr++) {+ case 0:+ if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') {+ *bom = GIT_BOM_UTF32_BE;+ return 4;+ }+ break;+ case '\xEF':+ if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') {+ *bom = GIT_BOM_UTF8;+ return 3;+ }+ break;+ case '\xFE':+ if (*ptr == '\xFF') {+ *bom = GIT_BOM_UTF16_BE;+ return 2;+ }+ break;+ case '\xFF':+ if (*ptr != '\xFE')+ break;+ if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) {+ *bom = GIT_BOM_UTF32_LE;+ return 4;+ } else {+ *bom = GIT_BOM_UTF16_LE;+ return 2;+ }+ break;+ default:+ break;+ }++ return 0;+}++bool git_buf_text_gather_stats(+ git_buf_text_stats *stats, const git_buf *buf, bool skip_bom)+{+ const char *scan = buf->ptr, *end = buf->ptr + buf->size;+ int skip;++ memset(stats, 0, sizeof(*stats));++ /* BOM detection */+ skip = git_buf_text_detect_bom(&stats->bom, buf, 0);+ if (skip_bom)+ scan += skip;++ /* Ignore EOF character */+ if (buf->size > 0 && end[-1] == '\032')+ end--;++ /* Counting loop */+ while (scan < end) {+ unsigned char c = *scan++;++ if ((c > 0x1F && c < 0x7F) || c > 0x9f)+ stats->printable++;+ else switch (c) {+ case '\0':+ stats->nul++;+ stats->nonprintable++;+ break;+ case '\n':+ stats->lf++;+ break;+ case '\r':+ stats->cr++;+ if (scan < end && *scan == '\n')+ stats->crlf++;+ break;+ case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/+ stats->printable++;+ break;+ default:+ stats->nonprintable++;+ break;+ }+ }++ return (stats->nul > 0 ||+ ((stats->printable >> 7) < stats->nonprintable));+}
+ libgit2/src/buf_text.h view
@@ -0,0 +1,122 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_buf_text_h__+#define INCLUDE_buf_text_h__++#include "buffer.h"++typedef enum {+ GIT_BOM_NONE = 0,+ GIT_BOM_UTF8 = 1,+ GIT_BOM_UTF16_LE = 2,+ GIT_BOM_UTF16_BE = 3,+ GIT_BOM_UTF32_LE = 4,+ GIT_BOM_UTF32_BE = 5+} git_bom_t;++typedef struct {+ git_bom_t bom; /* BOM found at head of text */+ unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */+ unsigned int printable, nonprintable; /* These are just approximations! */+} git_buf_text_stats;++/**+ * Append string to buffer, prefixing each character from `esc_chars` with+ * `esc_with` string.+ *+ * @param buf Buffer to append data to+ * @param string String to escape and append+ * @param esc_chars Characters to be escaped+ * @param esc_with String to insert in from of each found character+ * @return 0 on success, <0 on failure (probably allocation problem)+ */+extern int git_buf_text_puts_escaped(+ git_buf *buf,+ const char *string,+ const char *esc_chars,+ const char *esc_with);++/**+ * Append string escaping characters that are regex special+ */+GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string)+{+ return git_buf_text_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\");+}++/**+ * Unescape all characters in a buffer in place+ *+ * I.e. remove backslashes+ */+extern void git_buf_text_unescape(git_buf *buf);++/**+ * Replace all \r\n with \n (or do nothing if no \r\n are found)+ *+ * @return 0 on success, GIT_ENOTFOUND if no \r\n, -1 on memory error+ */+extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);++/**+ * Replace all \n with \r\n (or do nothing if no \n are found)+ *+ * @return 0 on success, GIT_ENOTFOUND if no \n, -1 on memory error+ */+extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src);++/**+ * Fill buffer with the common prefix of a array of strings+ *+ * Buffer will be set to empty if there is no common prefix+ */+extern int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strs);++/**+ * Check quickly if buffer looks like it contains binary data+ *+ * @param buf Buffer to check+ * @return true if buffer looks like non-text data+ */+extern bool git_buf_text_is_binary(const git_buf *buf);++/**+ * Check quickly if buffer contains a NUL byte+ *+ * @param buf Buffer to check+ * @return true if buffer contains a NUL byte+ */+extern bool git_buf_text_contains_nul(const git_buf *buf);++/**+ * Check if a buffer begins with a UTF BOM+ *+ * @param bom Set to the type of BOM detected or GIT_BOM_NONE+ * @param buf Buffer in which to check the first bytes for a BOM+ * @param offset Offset into buffer to look for BOM+ * @return Number of bytes of BOM data (or 0 if no BOM found)+ */+extern int git_buf_text_detect_bom(+ git_bom_t *bom, const git_buf *buf, size_t offset);++/**+ * Gather stats for a piece of text+ *+ * Fill the `stats` structure with counts of unreadable characters, carriage+ * returns, etc, so it can be used in heuristics. This automatically skips+ * a trailing EOF (\032 character). Also it will look for a BOM at the+ * start of the text and can be told to skip that as well.+ *+ * @param stats Structure to be filled in+ * @param buf Text to process+ * @param skip_bom Exclude leading BOM from stats if true+ * @return Does the buffer heuristically look like binary data+ */+extern bool git_buf_text_gather_stats(+ git_buf_text_stats *stats, const git_buf *buf, bool skip_bom);++#endif
libgit2/src/buffer.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -31,15 +31,7 @@ git_buf_grow(buf, initial_size); } -int git_buf_grow(git_buf *buf, size_t target_size)-{- int error = git_buf_try_grow(buf, target_size);- if (error != 0)- buf->ptr = git_buf__oom;- return error;-}--int git_buf_try_grow(git_buf *buf, size_t target_size)+int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom) { char *new_ptr; size_t new_size;@@ -67,8 +59,12 @@ new_size = (new_size + 7) & ~7; new_ptr = git__realloc(new_ptr, new_size);- if (!new_ptr)++ if (!new_ptr) {+ if (mark_oom)+ buf->ptr = git_buf__oom; return -1;+ } buf->asize = new_size; buf->ptr = new_ptr;@@ -141,11 +137,52 @@ return git_buf_put(buf, string, strlen(string)); } +static const char b64str[64] =+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";++int git_buf_put_base64(git_buf *buf, const char *data, size_t len)+{+ size_t extra = len % 3;+ uint8_t *write, a, b, c;+ const uint8_t *read = (const uint8_t *)data;++ ENSURE_SIZE(buf, buf->size + 4 * ((len / 3) + !!extra) + 1);+ write = (uint8_t *)&buf->ptr[buf->size];++ /* convert each run of 3 bytes into 4 output bytes */+ for (len -= extra; len > 0; len -= 3) {+ a = *read++;+ b = *read++;+ c = *read++;++ *write++ = b64str[a >> 2];+ *write++ = b64str[(a & 0x03) << 4 | b >> 4];+ *write++ = b64str[(b & 0x0f) << 2 | c >> 6];+ *write++ = b64str[c & 0x3f];+ }++ if (extra > 0) {+ a = *read++;+ b = (extra > 1) ? *read++ : 0;++ *write++ = b64str[a >> 2];+ *write++ = b64str[(a & 0x03) << 4 | b >> 4];+ *write++ = (extra > 1) ? b64str[(b & 0x0f) << 2] : '=';+ *write++ = '=';+ }++ buf->size = ((char *)write) - buf->ptr;+ buf->ptr[buf->size] = '\0';++ return 0;+}+ int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) { int len;+ const size_t expected_size = buf->size + (strlen(format) * 2); - ENSURE_SIZE(buf, buf->size + (strlen(format) * 2));+ ENSURE_SIZE(buf, expected_size); while (1) { va_list args;@@ -411,51 +448,30 @@ (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0; } -int git_buf_common_prefix(git_buf *buf, const git_strarray *strings)+int git_buf_splice(+ git_buf *buf,+ size_t where,+ size_t nb_to_remove,+ const char *data,+ size_t nb_to_insert) {- size_t i;- const char *str, *pfx;-- git_buf_clear(buf);-- if (!strings || !strings->count)- return 0;+ assert(buf &&+ where <= git_buf_len(buf) &&+ where + nb_to_remove <= git_buf_len(buf)); - /* initialize common prefix to first string */- if (git_buf_sets(buf, strings->strings[0]) < 0)+ /* Ported from git.git+ * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176+ */+ if (git_buf_grow(buf, git_buf_len(buf) + nb_to_insert - nb_to_remove) < 0) return -1; - /* go through the rest of the strings, truncating to shared prefix */- for (i = 1; i < strings->count; ++i) {-- for (str = strings->strings[i], pfx = buf->ptr;- *str && *str == *pfx; str++, pfx++)- /* scanning */;-- git_buf_truncate(buf, pfx - buf->ptr);+ memmove(buf->ptr + where + nb_to_insert,+ buf->ptr + where + nb_to_remove,+ buf->size - where - nb_to_remove); - if (!buf->size)- break;- }+ memcpy(buf->ptr + where, data, nb_to_insert); + buf->size = buf->size + nb_to_insert - nb_to_remove;+ buf->ptr[buf->size] = '\0'; return 0; }--bool git_buf_is_binary(const git_buf *buf)-{- size_t i;- int printable = 0, nonprintable = 0;-- for (i = 0; i < buf->size; i++) {- unsigned char c = buf->ptr[i];- if (c > 0x1F && c < 0x7F)- printable++;- else if (c == '\0')- return true;- else if (!git__isspace(c))- nonprintable++;- }-- return ((printable >> 7) < nonprintable);-}-
libgit2/src/buffer.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,6 +8,7 @@ #define INCLUDE_buffer_h__ #include "common.h"+#include "git2/strarray.h" #include <stdarg.h> typedef struct {@@ -26,30 +27,35 @@ * For the cases where GIT_BUF_INIT cannot be used to do static * initialization. */-void git_buf_init(git_buf *buf, size_t initial_size);+extern void git_buf_init(git_buf *buf, size_t initial_size); /**- * Grow the buffer to hold at least `target_size` bytes.+ * Attempt to grow the buffer to hold at least `target_size` bytes. *- * If the allocation fails, this will return an error and the buffer- * will be marked as invalid for future operations. The existing- * contents of the buffer will be preserved however.- * @return 0 on success or -1 on failure+ * If the allocation fails, this will return an error. If mark_oom is true,+ * this will mark the buffer as invalid for future operations; if false,+ * existing buffer content will be preserved, but calling code must handle+ * that buffer was not expanded. */-int git_buf_grow(git_buf *buf, size_t target_size);+extern int git_buf_try_grow(git_buf *buf, size_t target_size, bool mark_oom); /**- * Attempt to grow the buffer to hold at least `target_size` bytes.+ * Grow the buffer to hold at least `target_size` bytes. *- * This is just like `git_buf_grow` except that even if the allocation- * fails, the git_buf will still be left in a valid state.+ * If the allocation fails, this will return an error and the buffer will be+ * marked as invalid for future operations, invaliding contents.+ *+ * @return 0 on success or -1 on failure */-int git_buf_try_grow(git_buf *buf, size_t target_size);+GIT_INLINE(int) git_buf_grow(git_buf *buf, size_t target_size)+{+ return git_buf_try_grow(buf, target_size, true);+} -void git_buf_free(git_buf *buf);-void git_buf_swap(git_buf *buf_a, git_buf *buf_b);-char *git_buf_detach(git_buf *buf);-void git_buf_attach(git_buf *buf, char *ptr, size_t asize);+extern void git_buf_free(git_buf *buf);+extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b);+extern char *git_buf_detach(git_buf *buf);+extern void git_buf_attach(git_buf *buf, char *ptr, size_t asize); /** * Test if there have been any reallocation failures with this git_buf.@@ -113,7 +119,7 @@ #define git_buf_PUTS(buf, str) git_buf_put(buf, str, sizeof(str) - 1) -GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch)+GIT_INLINE(ssize_t) git_buf_rfind_next(const git_buf *buf, char ch) { ssize_t idx = (ssize_t)buf->size - 1; while (idx >= 0 && buf->ptr[idx] == ch) idx--;@@ -121,15 +127,50 @@ return idx; } +GIT_INLINE(ssize_t) git_buf_rfind(const git_buf *buf, char ch)+{+ ssize_t idx = (ssize_t)buf->size - 1;+ while (idx >= 0 && buf->ptr[idx] != ch) idx--;+ return idx;+}++GIT_INLINE(ssize_t) git_buf_find(const git_buf *buf, char ch)+{+ void *found = memchr(buf->ptr, ch, buf->size);+ return found ? (ssize_t)((const char *)found - buf->ptr) : -1;+}+ /* Remove whitespace from the end of the buffer */ void git_buf_rtrim(git_buf *buf); int git_buf_cmp(const git_buf *a, const git_buf *b); -/* Fill buf with the common prefix of a array of strings */-int git_buf_common_prefix(git_buf *buf, const git_strarray *strings);+/* Write data as base64 encoded in buffer */+int git_buf_put_base64(git_buf *buf, const char *data, size_t len); -/* Check if buffer looks like it contains binary data */-bool git_buf_is_binary(const git_buf *buf);+/*+ * Insert, remove or replace a portion of the buffer.+ *+ * @param buf The buffer to work with+ *+ * @param where The location in the buffer where the transformation+ * should be applied.+ *+ * @param nb_to_remove The number of chars to be removed. 0 to not+ * remove any character in the buffer.+ *+ * @param data A pointer to the data which should be inserted.+ *+ * @param nb_to_insert The number of chars to be inserted. 0 to not+ * insert any character from the buffer.+ *+ * @return 0 or an error code.+ */+int git_buf_splice(+ git_buf *buf,+ size_t where,+ size_t nb_to_remove,+ const char *data,+ size_t nb_to_insert); #endif
libgit2/src/cache.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -11,6 +11,7 @@ #include "thread-utils.h" #include "util.h" #include "cache.h"+#include "git2/oid.h" int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) {@@ -40,6 +41,7 @@ git_cached_obj_decref(cache->nodes[i], cache->free_obj); } + git_mutex_free(&cache->lock); git__free(cache->nodes); } @@ -50,7 +52,11 @@ memcpy(&hash, oid->id, sizeof(hash)); - git_mutex_lock(&cache->lock);+ if (git_mutex_lock(&cache->lock)) {+ giterr_set(GITERR_THREAD, "unable to lock cache mutex");+ return NULL;+ }+ { node = cache->nodes[hash & cache->size_mask]; @@ -69,16 +75,20 @@ git_cached_obj *entry = _entry; uint32_t hash; - memcpy(&hash, &entry->oid, sizeof(hash));+ memcpy(&hash, &entry->oid, sizeof(uint32_t)); - /* increase the refcount on this object, because- * the cache now owns it */- git_cached_obj_incref(entry);+ if (git_mutex_lock(&cache->lock)) {+ giterr_set(GITERR_THREAD, "unable to lock cache mutex");+ return NULL;+ } - git_mutex_lock(&cache->lock); { git_cached_obj *node = cache->nodes[hash & cache->size_mask]; + /* increase the refcount on this object, because+ * the cache now owns it */+ git_cached_obj_incref(entry);+ if (node == NULL) { cache->nodes[hash & cache->size_mask] = entry; } else if (git_oid_cmp(&node->oid, &entry->oid) == 0) {@@ -88,12 +98,13 @@ git_cached_obj_decref(node, cache->free_obj); cache->nodes[hash & cache->size_mask] = entry; }++ /* increase the refcount again, because we are+ * returning it to the user */+ git_cached_obj_incref(entry);+ } git_mutex_unlock(&cache->lock);-- /* increase the refcount again, because we are- * returning it to the user */- git_cached_obj_incref(entry); return entry; }
libgit2/src/cache.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/cc-compat.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -38,8 +38,10 @@ /* Define the printf format specifer to use for size_t output */ #if defined(_MSC_VER) || defined(__MINGW32__) # define PRIuZ "Iu"+# define PRIxZ "Ix" #else # define PRIuZ "zu"+# define PRIxZ "zx" #endif /* Micosoft Visual C/C++ */
+ libgit2/src/checkout.c view
@@ -0,0 +1,1383 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include <assert.h>++#include "checkout.h"++#include "git2/repository.h"+#include "git2/refs.h"+#include "git2/tree.h"+#include "git2/blob.h"+#include "git2/config.h"+#include "git2/diff.h"+#include "git2/submodule.h"++#include "refs.h"+#include "repository.h"+#include "filter.h"+#include "blob.h"+#include "diff.h"+#include "pathspec.h"+#include "buf_text.h"++/* See docs/checkout-internals.md for more information */++enum {+ CHECKOUT_ACTION__NONE = 0,+ CHECKOUT_ACTION__REMOVE = 1,+ CHECKOUT_ACTION__UPDATE_BLOB = 2,+ CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,+ CHECKOUT_ACTION__CONFLICT = 8,+ CHECKOUT_ACTION__MAX = 8,+ CHECKOUT_ACTION__DEFER_REMOVE = 16,+ CHECKOUT_ACTION__REMOVE_AND_UPDATE =+ (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE),+};++typedef struct {+ git_repository *repo;+ git_diff_list *diff;+ git_checkout_opts opts;+ bool opts_free_baseline;+ char *pfx;+ git_index *index;+ git_pool pool;+ git_vector removes;+ git_buf path;+ size_t workdir_len;+ unsigned int strategy;+ int can_symlink;+ bool reload_submodules;+ size_t total_steps;+ size_t completed_steps;+} checkout_data;++static int checkout_notify(+ checkout_data *data,+ git_checkout_notify_t why,+ const git_diff_delta *delta,+ const git_index_entry *wditem)+{+ git_diff_file wdfile;+ const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL;+ const char *path = NULL;++ if (!data->opts.notify_cb)+ return 0;++ if ((why & data->opts.notify_flags) == 0)+ return 0;++ if (wditem) {+ memset(&wdfile, 0, sizeof(wdfile));++ git_oid_cpy(&wdfile.oid, &wditem->oid);+ wdfile.path = wditem->path;+ wdfile.size = wditem->file_size;+ wdfile.flags = GIT_DIFF_FLAG_VALID_OID;+ wdfile.mode = wditem->mode;++ workdir = &wdfile;++ path = wditem->path;+ }++ if (delta) {+ switch (delta->status) {+ case GIT_DELTA_UNMODIFIED:+ case GIT_DELTA_MODIFIED:+ case GIT_DELTA_TYPECHANGE:+ default:+ baseline = &delta->old_file;+ target = &delta->new_file;+ break;+ case GIT_DELTA_ADDED:+ case GIT_DELTA_IGNORED:+ case GIT_DELTA_UNTRACKED:+ target = &delta->new_file;+ break;+ case GIT_DELTA_DELETED:+ baseline = &delta->old_file;+ break;+ }++ path = delta->old_file.path;+ }++ return data->opts.notify_cb(+ why, path, baseline, target, workdir, data->opts.notify_payload);+}++static bool checkout_is_workdir_modified(+ checkout_data *data,+ const git_diff_file *baseitem,+ const git_index_entry *wditem)+{+ git_oid oid;++ /* handle "modified" submodule */+ if (wditem->mode == GIT_FILEMODE_COMMIT) {+ git_submodule *sm;+ unsigned int sm_status = 0;+ const git_oid *sm_oid = NULL;++ if (git_submodule_lookup(&sm, data->repo, wditem->path) < 0 ||+ git_submodule_status(&sm_status, sm) < 0)+ return true;++ if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))+ return true;++ sm_oid = git_submodule_wd_id(sm);+ if (!sm_oid)+ return false;++ return (git_oid_cmp(&baseitem->oid, sm_oid) != 0);+ }++ /* depending on where base is coming from, we may or may not know+ * the actual size of the data, so we can't rely on this shortcut.+ */+ if (baseitem->size && wditem->file_size != baseitem->size)+ return true;++ if (git_diff__oid_for_file(+ data->repo, wditem->path, wditem->mode,+ wditem->file_size, &oid) < 0)+ return false;++ return (git_oid_cmp(&baseitem->oid, &oid) != 0);+}++#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \+ ((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO)++static int checkout_action_common(+ checkout_data *data,+ int action,+ const git_diff_delta *delta,+ const git_index_entry *wd)+{+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;++ if (action <= 0)+ return action;++ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)+ action = (action & ~CHECKOUT_ACTION__REMOVE);++ if ((action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {+ if (S_ISGITLINK(delta->new_file.mode))+ action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |+ CHECKOUT_ACTION__UPDATE_SUBMODULE;++ notify = GIT_CHECKOUT_NOTIFY_UPDATED;+ }++ if ((action & CHECKOUT_ACTION__CONFLICT) != 0)+ notify = GIT_CHECKOUT_NOTIFY_CONFLICT;++ if (notify != GIT_CHECKOUT_NOTIFY_NONE &&+ checkout_notify(data, notify, delta, wd) != 0)+ return GIT_EUSER;++ return action;+}++static int checkout_action_no_wd(+ checkout_data *data,+ const git_diff_delta *delta)+{+ int action = CHECKOUT_ACTION__NONE;++ switch (delta->status) {+ case GIT_DELTA_UNMODIFIED: /* case 12 */+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL))+ return GIT_EUSER;+ action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);+ break;+ case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */+ case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);+ break;+ case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/+ if (delta->new_file.mode == GIT_FILEMODE_TREE)+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);+ break;+ case GIT_DELTA_DELETED: /* case 8 or 25 */+ default: /* impossible */+ break;+ }++ return checkout_action_common(data, action, delta, NULL);+}++static int checkout_action_wd_only(+ checkout_data *data,+ git_iterator *workdir,+ const git_index_entry *wd,+ git_vector *pathspec)+{+ bool remove = false;+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;++ if (!git_pathspec_match_path(+ pathspec, wd->path,+ (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0,+ git_iterator_ignore_case(workdir), NULL))+ return 0;++ /* check if item is tracked in the index but not in the checkout diff */+ if (data->index != NULL) {+ if (wd->mode != GIT_FILEMODE_TREE) {+ int error;++ if ((error = git_index_find(NULL, data->index, wd->path)) == 0) {+ notify = GIT_CHECKOUT_NOTIFY_DIRTY;+ remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);+ } else if (error != GIT_ENOTFOUND)+ return error;+ } else {+ /* for tree entries, we have to see if there are any index+ * entries that are contained inside that tree+ */+ size_t pos = git_index__prefix_position(data->index, wd->path);+ const git_index_entry *e = git_index_get_byindex(data->index, pos);++ if (e != NULL && data->diff->pfxcomp(e->path, wd->path) == 0) {+ notify = GIT_CHECKOUT_NOTIFY_DIRTY;+ remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);+ }+ }+ }++ if (notify != GIT_CHECKOUT_NOTIFY_NONE)+ /* found in index */;+ else if (git_iterator_current_is_ignored(workdir)) {+ notify = GIT_CHECKOUT_NOTIFY_IGNORED;+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0);+ }+ else {+ notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0);+ }++ if (checkout_notify(data, notify, NULL, wd))+ return GIT_EUSER;++ if (remove) {+ char *path = git_pool_strdup(&data->pool, wd->path);+ GITERR_CHECK_ALLOC(path);++ if (git_vector_insert(&data->removes, path) < 0)+ return -1;+ }++ return 0;+}++static bool submodule_is_config_only(+ checkout_data *data,+ const char *path)+{+ git_submodule *sm = NULL;+ unsigned int sm_loc = 0;++ if (git_submodule_lookup(&sm, data->repo, path) < 0 ||+ git_submodule_location(&sm_loc, sm) < 0 ||+ sm_loc == GIT_SUBMODULE_STATUS_IN_CONFIG)+ return true;++ return false;+}++static int checkout_action_with_wd(+ checkout_data *data,+ const git_diff_delta *delta,+ const git_index_entry *wd)+{+ int action = CHECKOUT_ACTION__NONE;++ switch (delta->status) {+ case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */+ if (checkout_is_workdir_modified(data, &delta->old_file, wd)) {+ if (checkout_notify(+ data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))+ return GIT_EUSER;+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE);+ }+ break;+ case GIT_DELTA_ADDED: /* case 3, 4 or 6 */+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);+ break;+ case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */+ if (checkout_is_workdir_modified(data, &delta->old_file, wd))+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);+ else+ action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);+ break;+ case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */+ if (checkout_is_workdir_modified(data, &delta->old_file, wd))+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);+ else+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);+ break;+ case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {+ if (wd->mode == GIT_FILEMODE_TREE)+ /* either deleting items in old tree will delete the wd dir,+ * or we'll get a conflict when we attempt blob update...+ */+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);+ else if (wd->mode == GIT_FILEMODE_COMMIT) {+ /* workdir is possibly a "phantom" submodule - treat as a+ * tree if the only submodule info came from the config+ */+ if (submodule_is_config_only(data, wd->path))+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);+ else+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);+ } else+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);+ }+ else if (checkout_is_workdir_modified(data, &delta->old_file, wd))+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);+ else+ action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);++ /* don't update if the typechange is to a tree */+ if (delta->new_file.mode == GIT_FILEMODE_TREE)+ action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB);+ break;+ default: /* impossible */+ break;+ }++ return checkout_action_common(data, action, delta, wd);+}++static int checkout_action_with_wd_blocker(+ checkout_data *data,+ const git_diff_delta *delta,+ const git_index_entry *wd)+{+ int action = CHECKOUT_ACTION__NONE;++ switch (delta->status) {+ case GIT_DELTA_UNMODIFIED:+ /* should show delta as dirty / deleted */+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))+ return GIT_EUSER;+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE);+ break;+ case GIT_DELTA_ADDED:+ case GIT_DELTA_MODIFIED:+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);+ break;+ case GIT_DELTA_DELETED:+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);+ break;+ case GIT_DELTA_TYPECHANGE:+ /* not 100% certain about this... */+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);+ break;+ default: /* impossible */+ break;+ }++ return checkout_action_common(data, action, delta, wd);+}++static int checkout_action_with_wd_dir(+ checkout_data *data,+ const git_diff_delta *delta,+ const git_index_entry *wd)+{+ int action = CHECKOUT_ACTION__NONE;++ switch (delta->status) {+ case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL) ||+ checkout_notify(+ data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd))+ return GIT_EUSER;+ break;+ case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */+ case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */+ if (delta->old_file.mode == GIT_FILEMODE_COMMIT)+ /* expected submodule (and maybe found one) */;+ else if (delta->new_file.mode != GIT_FILEMODE_TREE)+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);+ break;+ case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */+ if (delta->old_file.mode != GIT_FILEMODE_TREE &&+ checkout_notify(+ data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd))+ return GIT_EUSER;+ break;+ case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {+ /* For typechange from dir, remove dir and add blob, but it is+ * not safe to remove dir if it contains modified files.+ * However, safely removing child files will remove the parent+ * directory if is it left empty, so we can defer removing the+ * dir and it will succeed if no children are left.+ */+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);+ if (action != CHECKOUT_ACTION__NONE)+ action |= CHECKOUT_ACTION__DEFER_REMOVE;+ }+ else if (delta->new_file.mode != GIT_FILEMODE_TREE)+ /* For typechange to dir, dir is already created so no action */+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);+ break;+ default: /* impossible */+ break;+ }++ return checkout_action_common(data, action, delta, wd);+}++static int checkout_action(+ checkout_data *data,+ git_diff_delta *delta,+ git_iterator *workdir,+ const git_index_entry **wditem_ptr,+ git_vector *pathspec)+{+ const git_index_entry *wd = *wditem_ptr;+ int cmp = -1, act;+ int (*strcomp)(const char *, const char *) = data->diff->strcomp;+ int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp;++ /* move workdir iterator to follow along with deltas */++ while (1) {+ if (!wd)+ return checkout_action_no_wd(data, delta);++ cmp = strcomp(wd->path, delta->old_file.path);++ /* 1. wd before delta ("a/a" before "a/b")+ * 2. wd prefixes delta & should expand ("a/" before "a/b")+ * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c")+ * 4. wd equals delta ("a/b" and "a/b")+ * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b")+ * 6. wd after delta ("a/c" after "a/b")+ */++ if (cmp < 0) {+ cmp = pfxcomp(delta->old_file.path, wd->path);++ if (cmp == 0) {+ if (wd->mode == GIT_FILEMODE_TREE) {+ /* case 2 - entry prefixed by workdir tree */+ if (git_iterator_advance_into(&wd, workdir) < 0)+ goto fail;++ *wditem_ptr = wd;+ continue;+ }++ /* case 3 maybe - wd contains non-dir where dir expected */+ if (delta->old_file.path[strlen(wd->path)] == '/') {+ act = checkout_action_with_wd_blocker(data, delta, wd);+ *wditem_ptr =+ git_iterator_advance(&wd, workdir) ? NULL : wd;+ return act;+ }+ }++ /* case 1 - handle wd item (if it matches pathspec) */+ if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0 ||+ git_iterator_advance(&wd, workdir) < 0)+ goto fail;++ *wditem_ptr = wd;+ continue;+ }++ if (cmp == 0) {+ /* case 4 */+ act = checkout_action_with_wd(data, delta, wd);+ *wditem_ptr = git_iterator_advance(&wd, workdir) ? NULL : wd;+ return act;+ }++ cmp = pfxcomp(wd->path, delta->old_file.path);++ if (cmp == 0) { /* case 5 */+ if (wd->path[strlen(delta->old_file.path)] != '/')+ return checkout_action_no_wd(data, delta);++ if (delta->status == GIT_DELTA_TYPECHANGE) {+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {+ act = checkout_action_with_wd(data, delta, wd);+ if (git_iterator_advance_into(&wd, workdir) < 0)+ wd = NULL;+ *wditem_ptr = wd;+ return act;+ }++ if (delta->new_file.mode == GIT_FILEMODE_TREE ||+ delta->new_file.mode == GIT_FILEMODE_COMMIT ||+ delta->old_file.mode == GIT_FILEMODE_COMMIT)+ {+ act = checkout_action_with_wd(data, delta, wd);+ if (git_iterator_advance(&wd, workdir) < 0)+ wd = NULL;+ *wditem_ptr = wd;+ return act;+ }+ }++ return checkout_action_with_wd_dir(data, delta, wd);+ }++ /* case 6 - wd is after delta */+ return checkout_action_no_wd(data, delta);+ }++fail:+ *wditem_ptr = NULL;+ return -1;+}++static int checkout_remaining_wd_items(+ checkout_data *data,+ git_iterator *workdir,+ const git_index_entry *wd,+ git_vector *spec)+{+ int error = 0;++ while (wd && !error) {+ if (!(error = checkout_action_wd_only(data, workdir, wd, spec)))+ error = git_iterator_advance(&wd, workdir);+ }++ return error;+}++static int checkout_get_actions(+ uint32_t **actions_ptr,+ size_t **counts_ptr,+ checkout_data *data,+ git_iterator *workdir)+{+ int error = 0;+ const git_index_entry *wditem;+ git_vector pathspec = GIT_VECTOR_INIT, *deltas;+ git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;+ git_diff_delta *delta;+ size_t i, *counts = NULL;+ uint32_t *actions = NULL;++ if (data->opts.paths.count > 0 &&+ git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0)+ return -1;++ if ((error = git_iterator_current(&wditem, workdir)) < 0)+ goto fail;++ deltas = &data->diff->deltas;++ *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));+ *actions_ptr = actions = git__calloc(+ deltas->length ? deltas->length : 1, sizeof(uint32_t));+ if (!counts || !actions) {+ error = -1;+ goto fail;+ }++ git_vector_foreach(deltas, i, delta) {+ int act = checkout_action(data, delta, workdir, &wditem, &pathspec);++ if (act < 0) {+ error = act;+ goto fail;+ }++ actions[i] = act;++ if (act & CHECKOUT_ACTION__REMOVE)+ counts[CHECKOUT_ACTION__REMOVE]++;+ if (act & CHECKOUT_ACTION__UPDATE_BLOB)+ counts[CHECKOUT_ACTION__UPDATE_BLOB]++;+ if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++;+ if (act & CHECKOUT_ACTION__CONFLICT)+ counts[CHECKOUT_ACTION__CONFLICT]++;+ }++ error = checkout_remaining_wd_items(data, workdir, wditem, &pathspec);+ if (error < 0)+ goto fail;++ counts[CHECKOUT_ACTION__REMOVE] += data->removes.length;++ if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&+ (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)+ {+ giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",+ (int)counts[CHECKOUT_ACTION__CONFLICT]);+ error = GIT_EMERGECONFLICT;+ goto fail;+ }++ git_pathspec_free(&pathspec);+ git_pool_clear(&pathpool);++ return 0;++fail:+ *counts_ptr = NULL;+ git__free(counts);+ *actions_ptr = NULL;+ git__free(actions);++ git_pathspec_free(&pathspec);+ git_pool_clear(&pathpool);++ return error;+}++static int buffer_to_file(+ struct stat *st,+ git_buf *buffer,+ const char *path,+ mode_t dir_mode,+ int file_open_flags,+ mode_t file_mode)+{+ int fd, error;++ if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)+ return error;++ if ((fd = p_open(path, file_open_flags, file_mode)) < 0) {+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);+ return fd;+ }++ if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) {+ giterr_set(GITERR_OS, "Could not write to '%s'", path);+ (void)p_close(fd);+ } else {+ if ((error = p_close(fd)) < 0)+ giterr_set(GITERR_OS, "Error while closing '%s'", path);++ if ((error = p_stat(path, st)) < 0)+ giterr_set(GITERR_OS, "Error while statting '%s'", path);+ }++ if (!error &&+ (file_mode & 0100) != 0 &&+ (error = p_chmod(path, file_mode)) < 0)+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);++ return error;+}++static int blob_content_to_file(+ struct stat *st,+ git_blob *blob,+ const char *path,+ mode_t entry_filemode,+ git_checkout_opts *opts)+{+ int error = -1, nb_filters = 0;+ mode_t file_mode = opts->file_mode;+ bool dont_free_filtered;+ git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT;+ git_vector filters = GIT_VECTOR_INIT;++ /* Create a fake git_buf from the blob raw data... */+ filtered.ptr = blob->odb_object->raw.data;+ filtered.size = blob->odb_object->raw.len;+ /* ... and make sure it doesn't get unexpectedly freed */+ dont_free_filtered = true;++ if (!opts->disable_filters &&+ !git_buf_text_is_binary(&filtered) &&+ (nb_filters = git_filters_load(+ &filters,+ git_object_owner((git_object *)blob),+ path,+ GIT_FILTER_TO_WORKTREE)) > 0)+ {+ /* reset 'filtered' so it can be a filter target */+ git_buf_init(&filtered, 0);+ dont_free_filtered = false;+ }++ if (nb_filters < 0)+ return nb_filters;++ if (nb_filters > 0) {+ if ((error = git_blob__getbuf(&unfiltered, blob)) < 0)+ goto cleanup;++ if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0)+ goto cleanup;+ }++ /* Allow overriding of file mode */+ if (!file_mode)+ file_mode = entry_filemode;++ error = buffer_to_file(+ st, &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);++ if (!error)+ st->st_mode = entry_filemode;++cleanup:+ git_filters_free(&filters);+ git_buf_free(&unfiltered);+ if (!dont_free_filtered)+ git_buf_free(&filtered);++ return error;+}++static int blob_content_to_link(+ struct stat *st, git_blob *blob, const char *path, int can_symlink)+{+ git_buf linktarget = GIT_BUF_INIT;+ int error;++ if ((error = git_blob__getbuf(&linktarget, blob)) < 0)+ return error;++ if (can_symlink) {+ if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0)+ giterr_set(GITERR_CHECKOUT, "Could not create symlink %s\n", path);+ } else {+ error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);+ }++ if (!error) {+ if ((error = p_lstat(path, st)) < 0)+ giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path);++ st->st_mode = GIT_FILEMODE_LINK;+ }++ git_buf_free(&linktarget);++ return error;+}++static int checkout_update_index(+ checkout_data *data,+ const git_diff_file *file,+ struct stat *st)+{+ git_index_entry entry;++ if (!data->index)+ return 0;++ memset(&entry, 0, sizeof(entry));+ entry.path = (char *)file->path; /* cast to prevent warning */+ git_index_entry__init_from_stat(&entry, st);+ git_oid_cpy(&entry.oid, &file->oid);++ return git_index_add(data->index, &entry);+}++static int checkout_submodule(+ checkout_data *data,+ const git_diff_file *file)+{+ int error = 0;+ git_submodule *sm;++ /* Until submodules are supported, UPDATE_ONLY means do nothing here */+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)+ return 0;++ if ((error = git_futils_mkdir(+ file->path, git_repository_workdir(data->repo),+ data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)+ return error;++ if ((error = git_submodule_lookup(&sm, data->repo, file->path)) < 0)+ return error;++ /* TODO: Support checkout_strategy options. Two circumstances:+ * 1 - submodule already checked out, but we need to move the HEAD+ * to the new OID, or+ * 2 - submodule not checked out and we should recursively check it out+ *+ * Checkout will not execute a pull on the submodule, but a clone+ * command should probably be able to. Do we need a submodule callback?+ */++ /* update the index unless prevented */+ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) {+ struct stat st;++ git_buf_truncate(&data->path, data->workdir_len);+ if (git_buf_puts(&data->path, file->path) < 0)+ return -1;++ if ((error = p_stat(git_buf_cstr(&data->path), &st)) < 0) {+ giterr_set(+ GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);+ return error;+ }++ st.st_mode = GIT_FILEMODE_COMMIT;++ error = checkout_update_index(data, file, &st);+ }++ return error;+}++static void report_progress(+ checkout_data *data,+ const char *path)+{+ if (data->opts.progress_cb)+ data->opts.progress_cb(+ path, data->completed_steps, data->total_steps,+ data->opts.progress_payload);+}++static int checkout_safe_for_update_only(const char *path, mode_t expected_mode)+{+ struct stat st;++ if (p_lstat(path, &st) < 0) {+ /* if doesn't exist, then no error and no update */+ if (errno == ENOENT || errno == ENOTDIR)+ return 0;++ /* otherwise, stat error and no update */+ giterr_set(GITERR_OS, "Failed to stat file '%s'", path);+ return -1;+ }++ /* only safe for update if this is the same type of file */+ if ((st.st_mode & ~0777) == (expected_mode & ~0777))+ return 1;++ return 0;+}++static int checkout_blob(+ checkout_data *data,+ const git_diff_file *file)+{+ int error = 0;+ git_blob *blob;+ struct stat st;++ git_buf_truncate(&data->path, data->workdir_len);+ if (git_buf_puts(&data->path, file->path) < 0)+ return -1;++ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) {+ int rval = checkout_safe_for_update_only(+ git_buf_cstr(&data->path), file->mode);+ if (rval <= 0)+ return rval;+ }++ if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)+ return error;++ if (S_ISLNK(file->mode))+ error = blob_content_to_link(+ &st, blob, git_buf_cstr(&data->path), data->can_symlink);+ else+ error = blob_content_to_file(+ &st, blob, git_buf_cstr(&data->path), file->mode, &data->opts);++ git_blob_free(blob);++ /* if we try to create the blob and an existing directory blocks it from+ * being written, then there must have been a typechange conflict in a+ * parent directory - suppress the error and try to continue.+ */+ if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 &&+ (error == GIT_ENOTFOUND || error == GIT_EEXISTS))+ {+ giterr_clear();+ error = 0;+ }++ /* update the index unless prevented */+ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)+ error = checkout_update_index(data, file, &st);++ /* update the submodule data if this was a new .gitmodules file */+ if (!error && strcmp(file->path, ".gitmodules") == 0)+ data->reload_submodules = true;++ return error;+}++static int checkout_remove_the_old(+ unsigned int *actions,+ checkout_data *data)+{+ int error = 0;+ git_diff_delta *delta;+ const char *str;+ size_t i;+ const char *workdir = git_buf_cstr(&data->path);+ uint32_t flg = GIT_RMDIR_EMPTY_PARENTS |+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;++ git_buf_truncate(&data->path, data->workdir_len);++ git_vector_foreach(&data->diff->deltas, i, delta) {+ if (actions[i] & CHECKOUT_ACTION__REMOVE) {+ error = git_futils_rmdir_r(delta->old_file.path, workdir, flg);+ if (error < 0)+ return error;++ data->completed_steps++;+ report_progress(data, delta->old_file.path);++ if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 &&+ (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&+ data->index != NULL)+ {+ (void)git_index_remove(data->index, delta->old_file.path, 0);+ }+ }+ }++ git_vector_foreach(&data->removes, i, str) {+ error = git_futils_rmdir_r(str, workdir, flg);+ if (error < 0)+ return error;++ data->completed_steps++;+ report_progress(data, str);++ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&+ data->index != NULL)+ {+ if (str[strlen(str) - 1] == '/')+ (void)git_index_remove_directory(data->index, str, 0);+ else+ (void)git_index_remove(data->index, str, 0);+ }+ }++ return 0;+}++static int checkout_deferred_remove(git_repository *repo, const char *path)+{+#if 0+ int error = git_futils_rmdir_r(+ path, git_repository_workdir(repo), GIT_RMDIR_EMPTY_PARENTS);++ if (error == GIT_ENOTFOUND) {+ error = 0;+ giterr_clear();+ }++ return error;+#else+ GIT_UNUSED(repo);+ GIT_UNUSED(path);+ assert(false);+ return 0;+#endif+}++static int checkout_create_the_new(+ unsigned int *actions,+ checkout_data *data)+{+ int error = 0;+ git_diff_delta *delta;+ size_t i;++ git_vector_foreach(&data->diff->deltas, i, delta) {+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {+ /* this had a blocker directory that should only be removed iff+ * all of the contents of the directory were safely removed+ */+ if ((error = checkout_deferred_remove(+ data->repo, delta->old_file.path)) < 0)+ return error;+ }++ if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {+ error = checkout_blob(data, &delta->new_file);+ if (error < 0)+ return error;++ data->completed_steps++;+ report_progress(data, delta->new_file.path);+ }+ }++ return 0;+}++static int checkout_create_submodules(+ unsigned int *actions,+ checkout_data *data)+{+ int error = 0;+ git_diff_delta *delta;+ size_t i;++ /* initial reload of submodules if .gitmodules was changed */+ if (data->reload_submodules &&+ (error = git_submodule_reload_all(data->repo)) < 0)+ return error;++ git_vector_foreach(&data->diff->deltas, i, delta) {+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {+ /* this has a blocker directory that should only be removed iff+ * all of the contents of the directory were safely removed+ */+ if ((error = checkout_deferred_remove(+ data->repo, delta->old_file.path)) < 0)+ return error;+ }++ if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {+ int error = checkout_submodule(data, &delta->new_file);+ if (error < 0)+ return error;++ data->completed_steps++;+ report_progress(data, delta->new_file.path);+ }+ }++ /* final reload once submodules have been updated */+ return git_submodule_reload_all(data->repo);+}++static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)+{+ int error = 0;+ git_reference *ref = NULL;+ git_object *head;++ if (!(error = git_repository_head(&ref, repo)) &&+ !(error = git_reference_peel(&head, ref, GIT_OBJ_TREE)))+ *out = (git_tree *)head;++ git_reference_free(ref);++ return error;+}++static void checkout_data_clear(checkout_data *data)+{+ if (data->opts_free_baseline) {+ git_tree_free(data->opts.baseline);+ data->opts.baseline = NULL;+ }++ git_vector_free(&data->removes);+ git_pool_clear(&data->pool);++ git__free(data->pfx);+ data->pfx = NULL;++ git_buf_free(&data->path);++ git_index_free(data->index);+ data->index = NULL;+}++static int checkout_data_init(+ checkout_data *data,+ git_iterator *target,+ git_checkout_opts *proposed)+{+ int error = 0;+ git_config *cfg;+ git_repository *repo = git_iterator_owner(target);++ memset(data, 0, sizeof(*data));++ if (!repo) {+ giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing");+ return -1;+ }++ if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)+ return error;++ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)+ return error;++ data->repo = repo;++ GITERR_CHECK_VERSION(+ proposed, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts");++ if (!proposed)+ GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTS_VERSION);+ else+ memmove(&data->opts, proposed, sizeof(git_checkout_opts));++ /* refresh config and index content unless NO_REFRESH is given */+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) {+ if ((error = git_config_refresh(cfg)) < 0)+ goto cleanup;++ /* if we are checking out the index, don't reload,+ * otherwise get index and force reload+ */+ if ((data->index = git_iterator_get_index(target)) != NULL) {+ GIT_REFCOUNT_INC(data->index);+ } else {+ /* otherwise, grab and reload the index */+ if ((error = git_repository_index(&data->index, data->repo)) < 0 ||+ (error = git_index_read(data->index)) < 0)+ goto cleanup;++ /* clear the REUC when doing a tree or commit checkout */+ git_index_reuc_clear(data->index);+ }+ }++ /* if you are forcing, definitely allow safe updates */+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0)+ data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE;+ if ((data->opts.checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0)+ data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE;++ data->strategy = data->opts.checkout_strategy;++ /* opts->disable_filters is false by default */++ if (!data->opts.dir_mode)+ data->opts.dir_mode = GIT_DIR_MODE;++ if (!data->opts.file_open_flags)+ data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;++ data->pfx = git_pathspec_prefix(&data->opts.paths);++ error = git_config_get_bool(&data->can_symlink, cfg, "core.symlinks");+ if (error < 0) {+ if (error != GIT_ENOTFOUND)+ goto cleanup;++ /* If "core.symlinks" is not found anywhere, default to true. */+ data->can_symlink = true;+ giterr_clear();+ error = 0;+ }++ if (!data->opts.baseline) {+ data->opts_free_baseline = true;+ error = checkout_lookup_head_tree(&data->opts.baseline, repo);++ if (error == GIT_EORPHANEDHEAD) {+ error = 0;+ giterr_clear();+ }++ if (error < 0)+ goto cleanup;+ }++ if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||+ (error = git_pool_init(&data->pool, 1, 0)) < 0 ||+ (error = git_buf_puts(&data->path, git_repository_workdir(repo))) < 0)+ goto cleanup;++ data->workdir_len = git_buf_len(&data->path);++cleanup:+ if (error < 0)+ checkout_data_clear(data);++ return error;+}++int git_checkout_iterator(+ git_iterator *target,+ git_checkout_opts *opts)+{+ int error = 0;+ git_iterator *baseline = NULL, *workdir = NULL;+ checkout_data data = {0};+ git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;+ uint32_t *actions = NULL;+ size_t *counts = NULL;+ git_iterator_flag_t iterflags = 0;++ /* initialize structures and options */+ error = checkout_data_init(&data, target, opts);+ if (error < 0)+ return error;++ diff_opts.flags =+ GIT_DIFF_INCLUDE_UNMODIFIED |+ GIT_DIFF_INCLUDE_UNTRACKED |+ GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */+ GIT_DIFF_INCLUDE_IGNORED |+ GIT_DIFF_INCLUDE_TYPECHANGE |+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES |+ GIT_DIFF_SKIP_BINARY_CHECK;+ if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)+ diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;+ if (data.opts.paths.count > 0)+ diff_opts.pathspec = data.opts.paths;++ /* set up iterators */++ iterflags = git_iterator_ignore_case(target) ?+ GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE;++ if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||+ (error = git_iterator_for_workdir(+ &workdir, data.repo, iterflags | GIT_ITERATOR_DONT_AUTOEXPAND,+ data.pfx, data.pfx)) < 0 ||+ (error = git_iterator_for_tree(+ &baseline, data.opts.baseline, iterflags, data.pfx, data.pfx)) < 0)+ goto cleanup;++ /* Should not have case insensitivity mismatch */+ assert(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline));++ /* Generate baseline-to-target diff which will include an entry for+ * every possible update that might need to be made.+ */+ if ((error = git_diff__from_iterators(+ &data.diff, data.repo, baseline, target, &diff_opts)) < 0)+ goto cleanup;++ /* Loop through diff (and working directory iterator) building a list of+ * actions to be taken, plus look for conflicts and send notifications.+ */+ if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0)+ goto cleanup;++ data.total_steps = counts[CHECKOUT_ACTION__REMOVE] ++ counts[CHECKOUT_ACTION__UPDATE_BLOB] ++ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];++ report_progress(&data, NULL); /* establish 0 baseline */++ /* To deal with some order dependencies, perform remaining checkout+ * in three passes: removes, then update blobs, then update submodules.+ */+ if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&+ (error = checkout_remove_the_old(actions, &data)) < 0)+ goto cleanup;++ if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&+ (error = checkout_create_the_new(actions, &data)) < 0)+ goto cleanup;++ if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&+ (error = checkout_create_submodules(actions, &data)) < 0)+ goto cleanup;++ assert(data.completed_steps == data.total_steps);++cleanup:+ if (error == GIT_EUSER)+ giterr_clear();++ if (!error && data.index != NULL &&+ (data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)+ error = git_index_write(data.index);++ git_diff_list_free(data.diff);+ git_iterator_free(workdir);+ git_iterator_free(baseline);+ git__free(actions);+ git__free(counts);+ checkout_data_clear(&data);++ return error;+}++int git_checkout_index(+ git_repository *repo,+ git_index *index,+ git_checkout_opts *opts)+{+ int error;+ git_iterator *index_i;++ if ((error = git_repository__ensure_not_bare(repo, "checkout index")) < 0)+ return error;++ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)+ return error;+ GIT_REFCOUNT_INC(index);++ if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL)))+ error = git_checkout_iterator(index_i, opts);++ git_iterator_free(index_i);+ git_index_free(index);++ return error;+}++int git_checkout_tree(+ git_repository *repo,+ const git_object *treeish,+ git_checkout_opts *opts)+{+ int error;+ git_tree *tree = NULL;+ git_iterator *tree_i = NULL;++ if ((error = git_repository__ensure_not_bare(repo, "checkout tree")) < 0)+ return error;++ if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {+ giterr_set(+ GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");+ return -1;+ }++ if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL)))+ error = git_checkout_iterator(tree_i, opts);++ git_iterator_free(tree_i);+ git_tree_free(tree);++ return error;+}++int git_checkout_head(+ git_repository *repo,+ git_checkout_opts *opts)+{+ int error;+ git_tree *head = NULL;+ git_iterator *head_i = NULL;++ if ((error = git_repository__ensure_not_bare(repo, "checkout head")) < 0)+ return error;++ if (!(error = checkout_lookup_head_tree(&head, repo)) &&+ !(error = git_iterator_for_tree(&head_i, head, 0, NULL, NULL)))+ error = git_checkout_iterator(head_i, opts);++ git_iterator_free(head_i);+ git_tree_free(head);++ return error;+}
+ libgit2/src/checkout.h view
@@ -0,0 +1,24 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_checkout_h__+#define INCLUDE_checkout_h__++#include "git2/checkout.h"+#include "iterator.h"++#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)++/**+ * Update the working directory to match the target iterator. The+ * expected baseline value can be passed in via the checkout options+ * or else will default to the HEAD commit.+ */+extern int git_checkout_iterator(+ git_iterator *target,+ git_checkout_opts *opts);++#endif
+ libgit2/src/clone.c view
@@ -0,0 +1,466 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include <assert.h>++#include "git2/clone.h"+#include "git2/remote.h"+#include "git2/revparse.h"+#include "git2/branch.h"+#include "git2/config.h"+#include "git2/checkout.h"+#include "git2/commit.h"+#include "git2/tree.h"++#include "common.h"+#include "remote.h"+#include "fileops.h"+#include "refs.h"+#include "path.h"++static int create_branch(+ git_reference **branch,+ git_repository *repo,+ const git_oid *target,+ const char *name)+{+ git_commit *head_obj = NULL;+ git_reference *branch_ref = NULL;+ int error;++ /* Find the target commit */+ if ((error = git_commit_lookup(&head_obj, repo, target)) < 0)+ return error;++ /* Create the new branch */+ error = git_branch_create(&branch_ref, repo, name, head_obj, 0);++ git_commit_free(head_obj);++ if (!error)+ *branch = branch_ref;+ else+ git_reference_free(branch_ref);++ return error;+}++static int setup_tracking_config(+ git_repository *repo,+ const char *branch_name,+ const char *remote_name,+ const char *merge_target)+{+ git_config *cfg;+ git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT;+ int error = -1;++ if (git_repository_config__weakptr(&cfg, repo) < 0)+ return -1;++ if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0)+ goto cleanup;++ if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0)+ goto cleanup;++ if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0)+ goto cleanup;++ if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0)+ goto cleanup;++ error = 0;++cleanup:+ git_buf_free(&remote_key);+ git_buf_free(&merge_key);+ return error;+}++static int create_tracking_branch(+ git_reference **branch,+ git_repository *repo,+ const git_oid *target,+ const char *branch_name)+{+ int error;++ if ((error = create_branch(branch, repo, target, branch_name)) < 0)+ return error;++ return setup_tracking_config(+ repo,+ branch_name,+ GIT_REMOTE_ORIGIN,+ git_reference_name(*branch));+}++struct head_info {+ git_repository *repo;+ git_oid remote_head_oid;+ git_buf branchname;+ const git_refspec *refspec;+ bool found;+};++static int reference_matches_remote_head(+ const char *reference_name,+ void *payload)+{+ struct head_info *head_info = (struct head_info *)payload;+ git_oid oid;++ /* TODO: Should we guard against references+ * which name doesn't start with refs/heads/ ?+ */++ /* Stop looking if we've already found a match */+ if (head_info->found)+ return 0;++ if (git_reference_name_to_id(+ &oid,+ head_info->repo,+ reference_name) < 0) {+ /* If the reference doesn't exists, it obviously cannot match the expected oid. */+ giterr_clear();+ return 0;+ }++ if (git_oid_cmp(&head_info->remote_head_oid, &oid) == 0) {+ /* Determine the local reference name from the remote tracking one */+ if (git_refspec_transform_l(+ &head_info->branchname, + head_info->refspec,+ reference_name) < 0)+ return -1;+ + if (git_buf_len(&head_info->branchname) > 0) {+ if (git_buf_sets(+ &head_info->branchname,+ git_buf_cstr(&head_info->branchname) + strlen(GIT_REFS_HEADS_DIR)) < 0)+ return -1;++ head_info->found = 1;+ }+ }++ return 0;+}++static int update_head_to_new_branch(+ git_repository *repo,+ const git_oid *target,+ const char *name)+{+ git_reference *tracking_branch = NULL;+ int error;++ if ((error = create_tracking_branch(+ &tracking_branch,+ repo,+ target,+ name)) < 0)+ return error;++ error = git_repository_set_head(repo, git_reference_name(tracking_branch));++ git_reference_free(tracking_branch);++ return error;+}++static int get_head_callback(git_remote_head *head, void *payload)+{+ git_remote_head **destination = (git_remote_head **)payload;++ /* Save the first entry, and terminate the enumeration */+ *destination = head;+ return 1;+}++static int update_head_to_remote(git_repository *repo, git_remote *remote)+{+ int retcode = -1;+ git_remote_head *remote_head;+ struct head_info head_info;+ git_buf remote_master_name = GIT_BUF_INIT;++ /* Did we just clone an empty repository? */+ if (remote->refs.length == 0) {+ return setup_tracking_config(+ repo,+ "master",+ GIT_REMOTE_ORIGIN,+ GIT_REFS_HEADS_MASTER_FILE);+ }++ /* Get the remote's HEAD. This is always the first ref in remote->refs. */+ remote_head = NULL;+ + if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head))+ return -1;++ assert(remote_head);++ git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid);+ git_buf_init(&head_info.branchname, 16);+ head_info.repo = repo;+ head_info.refspec = git_remote_fetchspec(remote);+ head_info.found = 0;+ + /* Determine the remote tracking reference name from the local master */+ if (git_refspec_transform_r(+ &remote_master_name,+ head_info.refspec,+ GIT_REFS_HEADS_MASTER_FILE) < 0)+ return -1;++ /* Check to see if the remote HEAD points to the remote master */+ if (reference_matches_remote_head(git_buf_cstr(&remote_master_name), &head_info) < 0)+ goto cleanup;++ if (head_info.found) {+ retcode = update_head_to_new_branch(+ repo,+ &head_info.remote_head_oid,+ git_buf_cstr(&head_info.branchname));++ goto cleanup;+ }++ /* Not master. Check all the other refs. */+ if (git_reference_foreach(+ repo,+ GIT_REF_LISTALL,+ reference_matches_remote_head,+ &head_info) < 0)+ goto cleanup;++ if (head_info.found) {+ retcode = update_head_to_new_branch(+ repo,+ &head_info.remote_head_oid,+ git_buf_cstr(&head_info.branchname));++ goto cleanup;+ } else {+ retcode = git_repository_set_head_detached(+ repo,+ &head_info.remote_head_oid);+ goto cleanup;+ }++cleanup:+ git_buf_free(&remote_master_name);+ git_buf_free(&head_info.branchname);+ return retcode;+}++static int update_head_to_branch(+ git_repository *repo,+ const git_clone_options *options)+{+ int retcode;+ git_buf remote_branch_name = GIT_BUF_INIT;+ git_reference* remote_ref = NULL;+ + assert(options->checkout_branch);++ if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",+ options->remote_name, options->checkout_branch)) < 0 )+ goto cleanup;++ if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0)+ goto cleanup;++ retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref),+ options->checkout_branch);++cleanup:+ git_reference_free(remote_ref);+ git_buf_free(&remote_branch_name);+ return retcode;+}++/*+ * submodules?+ */++static int create_and_configure_origin(+ git_remote **out,+ git_repository *repo,+ const char *url,+ const git_clone_options *options)+{+ int error;+ git_remote *origin = NULL;++ if ((error = git_remote_create(&origin, repo, options->remote_name, url)) < 0)+ goto on_error;++ git_remote_set_cred_acquire_cb(origin, options->cred_acquire_cb,+ options->cred_acquire_payload);+ git_remote_set_autotag(origin, options->remote_autotag);+ /*+ * Don't write FETCH_HEAD, we'll check out the remote tracking+ * branch ourselves based on the server's default.+ */+ git_remote_set_update_fetchhead(origin, 0);++ if (options->remote_callbacks &&+ (error = git_remote_set_callbacks(origin, options->remote_callbacks)) < 0)+ goto on_error;++ if (options->fetch_spec &&+ (error = git_remote_set_fetchspec(origin, options->fetch_spec)) < 0)+ goto on_error;++ if (options->push_spec &&+ (error = git_remote_set_pushspec(origin, options->push_spec)) < 0)+ goto on_error;++ if (options->pushurl &&+ (error = git_remote_set_pushurl(origin, options->pushurl)) < 0)+ goto on_error;++ if ((error = git_remote_save(origin)) < 0)+ goto on_error;++ *out = origin;+ return 0;++on_error:+ git_remote_free(origin);+ return error;+}+++static int setup_remotes_and_fetch(+ git_repository *repo,+ const char *url,+ const git_clone_options *options)+{+ int retcode = GIT_ERROR;+ git_remote *origin;++ /* Construct an origin remote */+ if (!create_and_configure_origin(&origin, repo, url, options)) {+ git_remote_set_update_fetchhead(origin, 0);++ /* Connect and download everything */+ if (!git_remote_connect(origin, GIT_DIRECTION_FETCH)) {+ if (!(retcode = git_remote_download(origin, options->fetch_progress_cb,+ options->fetch_progress_payload))) {+ /* Create "origin/foo" branches for all remote branches */+ if (!git_remote_update_tips(origin)) {+ /* Point HEAD to the requested branch */+ if (options->checkout_branch) {+ if (!update_head_to_branch(repo, options))+ retcode = 0;+ }+ /* Point HEAD to the same ref as the remote's head */+ else if (!update_head_to_remote(repo, origin)) {+ retcode = 0;+ }+ }+ }+ git_remote_disconnect(origin);+ }+ git_remote_free(origin);+ }++ return retcode;+}+++static bool path_is_okay(const char *path)+{+ /* The path must either not exist, or be an empty directory */+ if (!git_path_exists(path)) return true;+ if (!git_path_is_empty_dir(path)) {+ giterr_set(GITERR_INVALID,+ "'%s' exists and is not an empty directory", path);+ return false;+ }+ return true;+}++static bool should_checkout(+ git_repository *repo,+ bool is_bare,+ git_checkout_opts *opts)+{+ if (is_bare)+ return false;++ if (!opts)+ return false;++ if (opts->checkout_strategy == GIT_CHECKOUT_NONE)+ return false;++ return !git_repository_head_orphan(repo);+}++static void normalize_options(git_clone_options *dst, const git_clone_options *src)+{+ git_clone_options default_options = GIT_CLONE_OPTIONS_INIT;+ if (!src) src = &default_options;++ *dst = *src;++ /* Provide defaults for null pointers */+ if (!dst->remote_name) dst->remote_name = "origin";+ if (!dst->remote_autotag) dst->remote_autotag = GIT_REMOTE_DOWNLOAD_TAGS_ALL;+}++int git_clone(+ git_repository **out,+ const char *url,+ const char *local_path,+ const git_clone_options *options)+{+ int retcode = GIT_ERROR;+ git_repository *repo = NULL;+ git_clone_options normOptions;+ int remove_directory_on_failure = 0;++ assert(out && url && local_path);++ normalize_options(&normOptions, options);+ GITERR_CHECK_VERSION(&normOptions, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");++ if (!path_is_okay(local_path)) {+ return GIT_ERROR;+ }++ /* Only remove the directory on failure if we create it */+ remove_directory_on_failure = !git_path_exists(local_path);++ if (!(retcode = git_repository_init(&repo, local_path, normOptions.bare))) {+ if ((retcode = setup_remotes_and_fetch(repo, url, &normOptions)) < 0) {+ /* Failed to fetch; clean up */+ git_repository_free(repo);++ if (remove_directory_on_failure)+ git_futils_rmdir_r(local_path, NULL, GIT_RMDIR_REMOVE_FILES);+ else+ git_futils_cleanupdir_r(local_path);++ } else {+ *out = repo;+ retcode = 0;+ }+ }++ if (!retcode && should_checkout(repo, normOptions.bare, &normOptions.checkout_opts))+ retcode = git_checkout_head(*out, &normOptions.checkout_opts);++ return retcode;+}
libgit2/src/commit.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -18,31 +18,22 @@ #include <stdarg.h> -#define COMMIT_BASIC_PARSE 0x0-#define COMMIT_FULL_PARSE 0x1--#define COMMIT_PRINT(commit) {\- char oid[41]; oid[40] = 0;\- git_oid_fmt(oid, &commit->object.id);\- printf("Oid: %s | In degree: %d | Time: %u\n", oid, commit->in_degree, commit->commit_time);\-}- static void clear_parents(git_commit *commit) {- unsigned int i;+ size_t i; - for (i = 0; i < commit->parent_oids.length; ++i) {- git_oid *parent = git_vector_get(&commit->parent_oids, i);+ for (i = 0; i < commit->parent_ids.length; ++i) {+ git_oid *parent = git_vector_get(&commit->parent_ids, i); git__free(parent); } - git_vector_clear(&commit->parent_oids);+ git_vector_clear(&commit->parent_ids); } void git_commit__free(git_commit *commit) { clear_parents(commit);- git_vector_free(&commit->parent_oids);+ git_vector_free(&commit->parent_ids); git_signature_free(commit->author); git_signature_free(commit->committer);@@ -52,11 +43,6 @@ git__free(commit); } -const git_oid *git_commit_id(git_commit *c)-{- return git_object_id((git_object *)c);-}- int git_commit_create_v( git_oid *oid, git_repository *repo,@@ -90,66 +76,6 @@ return res; } -/* Update the reference named `ref_name` so it points to `oid` */-static int update_reference(git_repository *repo, git_oid *oid, const char *ref_name)-{- git_reference *ref;- int res;-- res = git_reference_lookup(&ref, repo, ref_name);-- /* If we haven't found the reference at all, we assume we need to create- * a new reference and that's it */- if (res == GIT_ENOTFOUND) {- giterr_clear();- return git_reference_create_oid(NULL, repo, ref_name, oid, 1);- }-- if (res < 0)- return -1;-- /* If we have found a reference, but it's symbolic, we need to update- * the direct reference it points to */- if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {- git_reference *aux;- const char *sym_target;-- /* The target pointed at by this reference */- sym_target = git_reference_target(ref);-- /* resolve the reference to the target it points to */- res = git_reference_resolve(&aux, ref);-- /*- * if the symbolic reference pointed to an inexisting ref,- * this is means we're creating a new branch, for example.- * We need to create a new direct reference with that name- */- if (res == GIT_ENOTFOUND) {- giterr_clear();- res = git_reference_create_oid(NULL, repo, sym_target, oid, 1);- git_reference_free(ref);- return res;- }-- /* free the original symbolic reference now; not before because- * we're using the `sym_target` pointer */- git_reference_free(ref);-- if (res < 0)- return -1;-- /* store the newly found direct reference in its place */- ref = aux;- }-- /* ref is made to point to `oid`: ref is either the original reference,- * or the target of the symbolic reference we've looked up */- res = git_reference_set_oid(ref, oid);- git_reference_free(ref);- return res;-}- int git_commit_create_oid( git_oid *oid, git_repository *repo,@@ -162,7 +88,7 @@ int parent_count, const git_oid *parents[]) {- git_buf commit = GIT_BUF_INIT, cleaned_message = GIT_BUF_INIT;+ git_buf commit = GIT_BUF_INIT; int i; git_odb *odb; @@ -179,19 +105,8 @@ git_buf_putc(&commit, '\n'); -#if 0- /* Remove comments by default */- if (git_message_prettify(&cleaned_message, message, 1) < 0)- goto on_error;-- if (git_buf_puts(&commit, git_buf_cstr(&cleaned_message)) < 0)- goto on_error;-- git_buf_free(&cleaned_message);-#else if (git_buf_puts(&commit, message) < 0) goto on_error;-#endif if (git_repository_odb__weakptr(&odb, repo) < 0) goto on_error;@@ -202,13 +117,12 @@ git_buf_free(&commit); if (update_ref != NULL)- return update_reference(repo, oid, update_ref);+ return git_reference__update_terminal(repo, update_ref, oid); return 0; on_error: git_buf_free(&commit);- git_buf_free(&cleaned_message); giterr_set(GITERR_OBJECT, "Failed to create commit."); return -1; }@@ -251,27 +165,25 @@ { const char *buffer = data; const char *buffer_end = (const char *)data + len;-- git_oid parent_oid;+ git_oid parent_id; - git_vector_init(&commit->parent_oids, 4, NULL);+ if (git_vector_init(&commit->parent_ids, 4, NULL) < 0)+ return -1; - if (git_oid__parse(&commit->tree_oid, &buffer, buffer_end, "tree ") < 0)+ if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) goto bad_buffer; /* * TODO: commit grafts! */ - while (git_oid__parse(&parent_oid, &buffer, buffer_end, "parent ") == 0) {- git_oid *new_oid;-- new_oid = git__malloc(sizeof(git_oid));- GITERR_CHECK_ALLOC(new_oid);+ while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {+ git_oid *new_id = git__malloc(sizeof(git_oid));+ GITERR_CHECK_ALLOC(new_id); - git_oid_cpy(new_oid, &parent_oid);+ git_oid_cpy(new_id, &parent_id); - if (git_vector_insert(&commit->parent_oids, new_oid) < 0)+ if (git_vector_insert(&commit->parent_ids, new_id) < 0) return -1; } @@ -288,24 +200,30 @@ if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) return -1; - if (git__prefixcmp(buffer, "encoding ") == 0) {- const char *encoding_end;- buffer += strlen("encoding ");+ /* Parse add'l header entries until blank line found */+ while (buffer < buffer_end && *buffer != '\n') {+ const char *eoln = buffer;+ while (eoln < buffer_end && *eoln != '\n')+ ++eoln; - encoding_end = buffer;- while (encoding_end < buffer_end && *encoding_end != '\n')- encoding_end++;+ if (git__prefixcmp(buffer, "encoding ") == 0) {+ buffer += strlen("encoding "); - commit->message_encoding = git__strndup(buffer, encoding_end - buffer);- GITERR_CHECK_ALLOC(commit->message_encoding);+ commit->message_encoding = git__strndup(buffer, eoln - buffer);+ GITERR_CHECK_ALLOC(commit->message_encoding);+ } - buffer = encoding_end;+ if (eoln < buffer_end && *eoln == '\n')+ ++eoln;++ buffer = eoln; } - /* parse commit message */- while (buffer < buffer_end - 1 && *buffer == '\n')+ /* buffer is now at the end of the header, double-check and move forward into the message */+ if (buffer < buffer_end && *buffer == '\n') buffer++; + /* parse commit message */ if (buffer <= buffer_end) { commit->message = git__strndup(buffer, buffer_end - buffer); GITERR_CHECK_ALLOC(commit->message);@@ -325,7 +243,7 @@ } #define GIT_COMMIT_GETTER(_rvalue, _name, _return) \- _rvalue git_commit_##_name(git_commit *commit) \+ _rvalue git_commit_##_name(const git_commit *commit) \ {\ assert(commit); \ return _return; \@@ -337,33 +255,66 @@ GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)-GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length)-GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid);+GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length)+GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id); +int git_commit_tree(git_tree **tree_out, const git_commit *commit)+{+ assert(commit);+ return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);+} -int git_commit_tree(git_tree **tree_out, git_commit *commit)+const git_oid *git_commit_parent_id(git_commit *commit, unsigned int n) { assert(commit);- return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_oid);++ return git_vector_get(&commit->parent_ids, n); } int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) {- git_oid *parent_oid;+ const git_oid *parent_id; assert(commit); - parent_oid = git_vector_get(&commit->parent_oids, n);- if (parent_oid == NULL) {+ parent_id = git_commit_parent_id(commit, n);+ if (parent_id == NULL) { giterr_set(GITERR_INVALID, "Parent %u does not exist", n); return GIT_ENOTFOUND; } - return git_commit_lookup(parent, commit->object.repo, parent_oid);+ return git_commit_lookup(parent, commit->object.repo, parent_id); } -const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n)+int git_commit_nth_gen_ancestor(+ git_commit **ancestor,+ const git_commit *commit,+ unsigned int n) {- assert(commit);+ git_commit *current, *parent;+ int error; - return git_vector_get(&commit->parent_oids, n);+ assert(ancestor && commit);++ current = (git_commit *)commit;++ if (n == 0)+ return git_commit_lookup(+ ancestor,+ commit->object.repo,+ git_object_id((const git_object *)commit));++ while (n--) {+ error = git_commit_parent(&parent, (git_commit *)current, 0);++ if (current != commit)+ git_commit_free(current);++ if (error < 0)+ return error;++ current = parent;+ }++ *ancestor = parent;+ return 0; }
libgit2/src/commit.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -17,8 +17,8 @@ struct git_commit { git_object object; - git_vector parent_oids;- git_oid tree_oid;+ git_vector parent_ids;+ git_oid tree_id; git_signature *author; git_signature *committer;
+ libgit2/src/commit_list.c view
@@ -0,0 +1,194 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "commit_list.h"+#include "common.h"+#include "revwalk.h"+#include "pool.h"+#include "odb.h"++int git_commit_list_time_cmp(void *a, void *b)+{+ git_commit_list_node *commit_a = (git_commit_list_node *)a;+ git_commit_list_node *commit_b = (git_commit_list_node *)b;++ return (commit_a->time < commit_b->time);+}++git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p)+{+ git_commit_list *new_list = git__malloc(sizeof(git_commit_list));+ if (new_list != NULL) {+ new_list->item = item;+ new_list->next = *list_p;+ }+ *list_p = new_list;+ return new_list;+}++git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p)+{+ git_commit_list **pp = list_p;+ git_commit_list *p;++ while ((p = *pp) != NULL) {+ if (git_commit_list_time_cmp(p->item, item) < 0)+ break;++ pp = &p->next;+ }++ return git_commit_list_insert(item, pp);+}++git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk)+{+ return (git_commit_list_node *)git_pool_malloc(&walk->commit_pool, COMMIT_ALLOC);+}++static int commit_error(git_commit_list_node *commit, const char *msg)+{+ char commit_oid[GIT_OID_HEXSZ + 1];+ git_oid_fmt(commit_oid, &commit->oid);+ commit_oid[GIT_OID_HEXSZ] = '\0';++ giterr_set(GITERR_ODB, "Failed to parse commit %s - %s", commit_oid, msg);++ return -1;+}++static git_commit_list_node **alloc_parents(+ git_revwalk *walk, git_commit_list_node *commit, size_t n_parents)+{+ if (n_parents <= PARENTS_PER_COMMIT)+ return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node));++ return (git_commit_list_node **)git_pool_malloc(+ &walk->commit_pool, (uint32_t)(n_parents * sizeof(git_commit_list_node *)));+}+++void git_commit_list_free(git_commit_list **list_p)+{+ git_commit_list *list = *list_p;++ if (list == NULL)+ return;++ while (list) {+ git_commit_list *temp = list;+ list = temp->next;+ git__free(temp);+ }++ *list_p = NULL;+}++git_commit_list_node *git_commit_list_pop(git_commit_list **stack)+{+ git_commit_list *top = *stack;+ git_commit_list_node *item = top ? top->item : NULL;++ if (top) {+ *stack = top->next;+ git__free(top);+ }+ return item;+}++static int commit_quick_parse(git_revwalk *walk, git_commit_list_node *commit, git_rawobj *raw)+{+ const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1;+ unsigned char *buffer = raw->data;+ unsigned char *buffer_end = buffer + raw->len;+ unsigned char *parents_start, *committer_start;+ int i, parents = 0;+ int commit_time;++ buffer += strlen("tree ") + GIT_OID_HEXSZ + 1;++ parents_start = buffer;+ while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) {+ parents++;+ buffer += parent_len;+ }++ commit->parents = alloc_parents(walk, commit, parents);+ GITERR_CHECK_ALLOC(commit->parents);++ buffer = parents_start;+ for (i = 0; i < parents; ++i) {+ git_oid oid;++ if (git_oid_fromstr(&oid, (char *)buffer + strlen("parent ")) < 0)+ return -1;++ commit->parents[i] = git_revwalk__commit_lookup(walk, &oid);+ if (commit->parents[i] == NULL)+ return -1;++ buffer += parent_len;+ }++ commit->out_degree = (unsigned short)parents;++ if ((committer_start = buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)+ return commit_error(commit, "object is corrupted");++ buffer++;++ if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL)+ return commit_error(commit, "object is corrupted");++ /* Skip trailing spaces */+ while (buffer > committer_start && git__isspace(*buffer))+ buffer--;++ /* Seek for the begining of the pack of digits */+ while (buffer > committer_start && git__isdigit(*buffer))+ buffer--;++ /* Skip potential timezone offset */+ if ((buffer > committer_start) && (*buffer == '+' || *buffer == '-')) {+ buffer--;++ while (buffer > committer_start && git__isspace(*buffer))+ buffer--;++ while (buffer > committer_start && git__isdigit(*buffer))+ buffer--;+ }++ if ((buffer == committer_start) || (git__strtol32(&commit_time, (char *)(buffer + 1), NULL, 10) < 0))+ return commit_error(commit, "cannot parse commit time");++ commit->time = (time_t)commit_time;+ commit->parsed = 1;+ return 0;+}++int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit)+{+ git_odb_object *obj;+ int error;++ if (commit->parsed)+ return 0;++ if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)+ return error;++ if (obj->raw.type != GIT_OBJ_COMMIT) {+ giterr_set(GITERR_INVALID, "Object is no commit object");+ error = -1;+ } else+ error = commit_quick_parse(walk, commit, &obj->raw);++ git_odb_object_free(obj);+ return error;+}+
+ libgit2/src/commit_list.h view
@@ -0,0 +1,49 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_commit_list_h__+#define INCLUDE_commit_list_h__++#include "git2/oid.h"++#define PARENT1 (1 << 0)+#define PARENT2 (1 << 1)+#define RESULT (1 << 2)+#define STALE (1 << 3)++#define PARENTS_PER_COMMIT 2+#define COMMIT_ALLOC \+ (sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *))++typedef struct git_commit_list_node {+ git_oid oid;+ uint32_t time;+ unsigned int seen:1,+ uninteresting:1,+ topo_delay:1,+ parsed:1,+ flags : 4;++ unsigned short in_degree;+ unsigned short out_degree;++ struct git_commit_list_node **parents;+} git_commit_list_node;++typedef struct git_commit_list {+ git_commit_list_node *item;+ struct git_commit_list *next;+} git_commit_list;++git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk);+int git_commit_list_time_cmp(void *a, void *b);+void git_commit_list_free(git_commit_list **list_p);+git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p);+git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p);+int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit);+git_commit_list_node *git_commit_list_pop(git_commit_list **stack);++#endif
libgit2/src/common.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -24,21 +24,24 @@ # include <io.h> # include <direct.h>+# include <winsock2.h> # include <windows.h> # include "win32/msvc-compat.h" # include "win32/mingw-compat.h"+# include "win32/error.h"+# include "win32/version.h" # ifdef GIT_THREADS # include "win32/pthread.h"-#endif--# define snprintf _snprintf+# endif #else-# include <unistd.h> +# include <unistd.h> # ifdef GIT_THREADS # include <pthread.h> # endif+#define GIT_STDLIB_CALL+ #endif #include "git2/types.h"@@ -48,25 +51,59 @@ #include <regex.h> -extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2);-#define git__throw(error, ...) \- (git___throw(__VA_ARGS__), error)+/**+ * Check a pointer allocation result, returning -1 if it failed.+ */+#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; } -extern void git___rethrow(const char *, ...) GIT_FORMAT_PRINTF(1, 2);-#define git__rethrow(error, ...) \- (git___rethrow(__VA_ARGS__), error)+/**+ * Check a return value and propogate result if non-zero.+ */+#define GITERR_CHECK_ERROR(code) \+ do { int _err = (code); if (_err < 0) return _err; } while (0) +/**+ * Set the error message for this thread, formatting as needed.+ */+void giterr_set(int error_class, const char *string, ...); -#define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }+/**+ * Set the error message for a regex failure, using the internal regex+ * error code lookup and return a libgit error code.+ */+int giterr_set_regex(const regex_t *regex, int error_code); -void giterr_set_oom(void);-void giterr_set(int error_class, const char *string, ...);-void giterr_clear(void);-void giterr_set_str(int error_class, const char *string);-void giterr_set_regex(const regex_t *regex, int error_code);+/**+ * Check a versioned structure for validity+ */+GIT_INLINE(int) giterr__check_version(const void *structure, unsigned int expected_max, const char *name)+{+ unsigned int actual; + if (!structure)+ return 0; -#include "util.h"+ actual = *(const unsigned int*)structure;+ if (actual > 0 && actual <= expected_max)+ return 0; + giterr_set(GITERR_INVALID, "Invalid version %d on %s", actual, name);+ return -1;+}+#define GITERR_CHECK_VERSION(S,V,N) if (giterr__check_version(S,V,N) < 0) return -1++/**+ * Initialize a structure with a version.+ */+GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version)+{+ memset(structure, 0, len);+ *((int*)structure) = version;+}+#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V)++/* NOTE: other giterr functions are in the public errors.h header file */++#include "util.h" #endif /* INCLUDE_common_h__ */
− libgit2/src/compat/fnmatch.h
@@ -1,27 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */-#ifndef INCLUDE_fnmatch__compat_h__-#define INCLUDE_fnmatch__compat_h__--#include "common.h"--#define FNM_NOMATCH 1 /* Match failed. */-#define FNM_NOSYS 2 /* Function not supported (unused). */--#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */-#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */-#define FNM_PERIOD 0x04 /* Period must be matched by period. */-#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */-#define FNM_CASEFOLD 0x10 /* Case insensitive search. */--#define FNM_IGNORECASE FNM_CASEFOLD-#define FNM_FILE_NAME FNM_PATHNAME--extern int p_fnmatch(const char *pattern, const char *string, int flags);--#endif /* _FNMATCH_H */-
+ libgit2/src/compress.c view
@@ -0,0 +1,53 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "compress.h"++#include <zlib.h>++#define BUFFER_SIZE (1024 * 1024)++int git__compress(git_buf *buf, const void *buff, size_t len)+{+ z_stream zs;+ char *zb;+ size_t have;++ memset(&zs, 0, sizeof(zs));+ if (deflateInit(&zs, Z_DEFAULT_COMPRESSION) != Z_OK)+ return -1;++ zb = git__malloc(BUFFER_SIZE);+ GITERR_CHECK_ALLOC(zb);++ zs.next_in = (void *)buff;+ zs.avail_in = (uInt)len;++ do {+ zs.next_out = (unsigned char *)zb;+ zs.avail_out = BUFFER_SIZE;++ if (deflate(&zs, Z_FINISH) == Z_STREAM_ERROR) {+ git__free(zb);+ return -1;+ }++ have = BUFFER_SIZE - (size_t)zs.avail_out;++ if (git_buf_put(buf, zb, have) < 0) {+ git__free(zb);+ return -1;+ }++ } while (zs.avail_out == 0);++ assert(zs.avail_in == 0);++ deflateEnd(&zs);+ git__free(zb);+ return 0;+}
+ libgit2/src/compress.h view
@@ -0,0 +1,16 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_compress_h__+#define INCLUDE_compress_h__++#include "common.h"++#include "buffer.h"++int git__compress(git_buf *buf, const void *buff, size_t len);++#endif /* INCLUDE_compress_h__ */
libgit2/src/config.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,6 +10,8 @@ #include "config.h" #include "git2/config.h" #include "vector.h"+#include "buf_text.h"+#include "config_file.h" #if GIT_WIN32 # include <windows.h> #endif@@ -17,21 +19,29 @@ #include <ctype.h> typedef struct {- git_config_file *file;- int priority;+ git_refcount rc;++ git_config_backend *file;+ unsigned int level; } file_internal; +static void file_internal_free(file_internal *internal)+{+ git_config_backend *file;++ file = internal->file;+ file->free(file);+ git__free(internal);+}+ static void config_free(git_config *cfg) {- unsigned int i;- git_config_file *file;+ size_t i; file_internal *internal; for(i = 0; i < cfg->files.length; ++i){ internal = git_vector_get(&cfg->files, i);- file = internal->file;- file->free(file);- git__free(internal);+ GIT_REFCOUNT_DEC(internal, file_internal_free); } git_vector_free(&cfg->files);@@ -51,7 +61,7 @@ const file_internal *bk_a = (const file_internal *)(a); const file_internal *bk_b = (const file_internal *)(b); - return bk_b->priority - bk_a->priority;+ return bk_b->level - bk_a->level; } int git_config_new(git_config **out)@@ -73,92 +83,253 @@ return 0; } -int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority)+int git_config_add_file_ondisk(+ git_config *cfg,+ const char *path,+ unsigned int level,+ int force) {- git_config_file *file = NULL;+ git_config_backend *file = NULL;+ int res; + assert(cfg && path);++ if (!git_path_isfile(path)) {+ giterr_set(GITERR_CONFIG, "Cannot find config file '%s'", path);+ return GIT_ENOTFOUND;+ }+ if (git_config_file__ondisk(&file, path) < 0) return -1; - if (git_config_add_file(cfg, file, priority) < 0) {+ if ((res = git_config_add_backend(cfg, file, level, force)) < 0) { /* * free manually; the file is not owned by the config * instance yet and will not be freed on cleanup */ file->free(file);- return -1;+ return res; } return 0; } -int git_config_open_ondisk(git_config **cfg, const char *path)+int git_config_open_ondisk(git_config **out, const char *path) {- if (git_config_new(cfg) < 0)- return -1;+ int error;+ git_config *config; - if (git_config_add_file_ondisk(*cfg, path, 1) < 0) {- git_config_free(*cfg);+ *out = NULL;++ if (git_config_new(&config) < 0) return -1;++ if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0)+ git_config_free(config);+ else+ *out = config;++ return error;+}++static int find_internal_file_by_level(+ file_internal **internal_out,+ const git_config *cfg,+ int level)+{+ int pos = -1;+ file_internal *internal;+ unsigned int i;++ /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file+ * which has the highest level. As config files are stored in a vector+ * sorted by decreasing order of level, getting the file at position 0+ * will do the job.+ */+ if (level == GIT_CONFIG_HIGHEST_LEVEL) {+ pos = 0;+ } else {+ git_vector_foreach(&cfg->files, i, internal) {+ if (internal->level == (unsigned int)level)+ pos = i;+ } } + if (pos == -1) {+ giterr_set(GITERR_CONFIG,+ "No config file exists for the given level '%i'", level);+ return GIT_ENOTFOUND;+ }++ *internal_out = git_vector_get(&cfg->files, pos);+ return 0; } -int git_config_add_file(git_config *cfg, git_config_file *file, int priority)+static int duplicate_level(void **old_raw, void *new_raw) {+ file_internal **old = (file_internal **)old_raw;++ GIT_UNUSED(new_raw);++ giterr_set(GITERR_CONFIG, "A file with the same level (%i) has already been added to the config", (*old)->level);+ return GIT_EEXISTS;+}++static void try_remove_existing_file_internal(+ git_config *cfg,+ unsigned int level)+{+ int pos = -1; file_internal *internal;+ unsigned int i;++ git_vector_foreach(&cfg->files, i, internal) {+ if (internal->level == level)+ pos = i;+ }++ if (pos == -1)+ return;++ internal = git_vector_get(&cfg->files, pos);++ if (git_vector_remove(&cfg->files, pos) < 0)+ return;++ GIT_REFCOUNT_DEC(internal, file_internal_free);+}++static int git_config__add_internal(+ git_config *cfg,+ file_internal *internal,+ unsigned int level,+ int force)+{ int result; + /* delete existing config file for level if it exists */+ if (force)+ try_remove_existing_file_internal(cfg, level);++ if ((result = git_vector_insert_sorted(&cfg->files,+ internal, &duplicate_level)) < 0)+ return result;++ git_vector_sort(&cfg->files);+ internal->file->cfg = cfg;++ GIT_REFCOUNT_INC(internal);++ return 0;+}++int git_config_open_level(+ git_config **cfg_out,+ const git_config *cfg_parent,+ unsigned int level)+{+ git_config *cfg;+ file_internal *internal;+ int res;++ if ((res = find_internal_file_by_level(&internal, cfg_parent, level)) < 0)+ return res;++ if ((res = git_config_new(&cfg)) < 0)+ return res;++ if ((res = git_config__add_internal(cfg, internal, level, true)) < 0) {+ git_config_free(cfg);+ return res;+ }++ *cfg_out = cfg;++ return 0;+}++int git_config_add_backend(+ git_config *cfg,+ git_config_backend *file,+ unsigned int level,+ int force)+{+ file_internal *internal;+ int result;+ assert(cfg && file); - if ((result = file->open(file)) < 0)+ GITERR_CHECK_VERSION(file, GIT_CONFIG_BACKEND_VERSION, "git_config_backend");++ if ((result = file->open(file, level)) < 0) return result; internal = git__malloc(sizeof(file_internal)); GITERR_CHECK_ALLOC(internal); + memset(internal, 0x0, sizeof(file_internal));+ internal->file = file;- internal->priority = priority;+ internal->level = level; - if (git_vector_insert(&cfg->files, internal) < 0) {+ if ((result = git_config__add_internal(cfg, internal, level, force)) < 0) { git__free(internal);- return -1;+ return result; } - git_vector_sort(&cfg->files);- internal->file->cfg = cfg;- return 0; } +int git_config_refresh(git_config *cfg)+{+ int error = 0;+ size_t i;++ for (i = 0; i < cfg->files.length && !error; ++i) {+ file_internal *internal = git_vector_get(&cfg->files, i);+ git_config_backend *file = internal->file;+ error = file->refresh(file);+ }++ return error;+}+ /* * Loop over all the variables */ -int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data)+int git_config_foreach(+ const git_config *cfg, git_config_foreach_cb cb, void *payload) {+ return git_config_foreach_match(cfg, NULL, cb, payload);+}++int git_config_foreach_match(+ const git_config *cfg,+ const char *regexp,+ git_config_foreach_cb cb,+ void *payload)+{ int ret = 0;- unsigned int i;+ size_t i; file_internal *internal;- git_config_file *file;+ git_config_backend *file; - for(i = 0; i < cfg->files.length && ret == 0; ++i) {+ for (i = 0; i < cfg->files.length && ret == 0; ++i) { internal = git_vector_get(&cfg->files, i); file = internal->file;- ret = file->foreach(file, fn, data);+ ret = file->foreach(file, regexp, cb, payload); } return ret; } -int git_config_delete(git_config *cfg, const char *name)+int git_config_delete_entry(git_config *cfg, const char *name) {+ git_config_backend *file; file_internal *internal;- git_config_file *file; - assert(cfg->files.length);- internal = git_vector_get(&cfg->files, 0); file = internal->file; @@ -188,10 +359,13 @@ int git_config_set_string(git_config *cfg, const char *name, const char *value) {+ git_config_backend *file; file_internal *internal;- git_config_file *file; - assert(cfg->files.length);+ if (!value) {+ giterr_set(GITERR_CONFIG, "The value to set cannot be NULL");+ return -1;+ } internal = git_vector_get(&cfg->files, 0); file = internal->file;@@ -199,211 +373,121 @@ return file->set(file, name, value); } -static int parse_int64(int64_t *out, const char *value)-{- const char *num_end;- int64_t num;-- if (git__strtol64(&num, value, &num_end, 0) < 0)- return -1;-- switch (*num_end) {- case 'g':- case 'G':- num *= 1024;- /* fallthrough */-- case 'm':- case 'M':- num *= 1024;- /* fallthrough */-- case 'k':- case 'K':- num *= 1024;-- /* check that that there are no more characters after the- * given modifier suffix */- if (num_end[1] != '\0')- return -1;-- /* fallthrough */-- case '\0':- *out = num;- return 0;-- default:- return -1;- }-}--static int parse_int32(int32_t *out, const char *value)-{- int64_t tmp;- int32_t truncate;-- if (parse_int64(&tmp, value) < 0)- return -1;-- truncate = tmp & 0xFFFFFFFF;- if (truncate != tmp)- return -1;-- *out = truncate;- return 0;-}- /*********** * Getters ***********/-int git_config_lookup_map_value(- git_cvar_map *maps, size_t map_n, const char *value, int *out)-{- size_t i;-- if (!value)- return GIT_ENOTFOUND;-- for (i = 0; i < map_n; ++i) {- git_cvar_map *m = maps + i;-- switch (m->cvar_type) {- case GIT_CVAR_FALSE:- case GIT_CVAR_TRUE: {- int bool_val;-- if (git__parse_bool(&bool_val, value) == 0 &&- bool_val == (int)m->cvar_type) {- *out = m->map_value;- return 0;- }- break;- }-- case GIT_CVAR_INT32:- if (parse_int32(out, value) == 0)- return 0;- break;-- case GIT_CVAR_STRING:- if (strcasecmp(value, m->str_match) == 0) {- *out = m->map_value;- return 0;- }- break;- }- }-- return GIT_ENOTFOUND;-}- int git_config_get_mapped( int *out,- git_config *cfg,+ const git_config *cfg, const char *name,- git_cvar_map *maps,+ const git_cvar_map *maps, size_t map_n) { const char *value; int ret; - ret = git_config_get_string(&value, cfg, name);- if (ret < 0)+ if ((ret = git_config_get_string(&value, cfg, name)) < 0) return ret; - if (!git_config_lookup_map_value(maps, map_n, value, out))- return 0;-- giterr_set(GITERR_CONFIG,- "Failed to map the '%s' config variable with a valid value", name);- return -1;+ return git_config_lookup_map_value(out, maps, map_n, value); } -int git_config_get_int64(int64_t *out, git_config *cfg, const char *name)+int git_config_get_int64(int64_t *out, const git_config *cfg, const char *name) { const char *value; int ret; - ret = git_config_get_string(&value, cfg, name);- if (ret < 0)+ if ((ret = git_config_get_string(&value, cfg, name)) < 0) return ret; - if (parse_int64(out, value) < 0) {- giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value);- return -1;- }-- return 0;+ return git_config_parse_int64(out, value); } -int git_config_get_int32(int32_t *out, git_config *cfg, const char *name)+int git_config_get_int32(int32_t *out, const git_config *cfg, const char *name) { const char *value; int ret; - ret = git_config_get_string(&value, cfg, name);- if (ret < 0)+ if ((ret = git_config_get_string(&value, cfg, name)) < 0) return ret; - if (parse_int32(out, value) < 0) {- giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);- return -1;+ return git_config_parse_int32(out, value);+}++static int get_string_at_file(const char **out, const git_config_backend *file, const char *name)+{+ const git_config_entry *entry;+ int res;++ res = file->get(file, name, &entry);+ if (!res)+ *out = entry->value;++ return res;+}++static int get_string(const char **out, const git_config *cfg, const char *name)+{+ file_internal *internal;+ unsigned int i;++ git_vector_foreach(&cfg->files, i, internal) {+ int res = get_string_at_file(out, internal->file, name);++ if (res != GIT_ENOTFOUND)+ return res; } - return 0;+ return GIT_ENOTFOUND; } -int git_config_get_bool(int *out, git_config *cfg, const char *name)+int git_config_get_bool(int *out, const git_config *cfg, const char *name) {- const char *value;+ const char *value = NULL; int ret; - ret = git_config_get_string(&value, cfg, name);- if (ret < 0)+ if ((ret = get_string(&value, cfg, name)) < 0) return ret; - if (git__parse_bool(out, value) == 0)- return 0;+ return git_config_parse_bool(out, value);+} - if (parse_int32(out, value) == 0) {- *out = !!(*out);- return 0;- }+int git_config_get_string(const char **out, const git_config *cfg, const char *name)+{+ int ret;+ const char *str = NULL; - giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value);- return -1;+ if ((ret = get_string(&str, cfg, name)) < 0)+ return ret;++ *out = str == NULL ? "" : str;+ return 0; } -int git_config_get_string(const char **out, git_config *cfg, const char *name)+int git_config_get_entry(const git_config_entry **out, const git_config *cfg, const char *name) { file_internal *internal; unsigned int i; - assert(cfg->files.length);- *out = NULL; git_vector_foreach(&cfg->files, i, internal) {- git_config_file *file = internal->file;+ git_config_backend *file = internal->file; int ret = file->get(file, name, out); if (ret != GIT_ENOTFOUND) return ret; } - giterr_set(GITERR_CONFIG, "Config variable '%s' not found", name); return GIT_ENOTFOUND; } -int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp,- int (*fn)(const char *value, void *data), void *data)+int git_config_get_multivar(const git_config *cfg, const char *name, const char *regexp,+ git_config_foreach_cb cb, void *payload) { file_internal *internal;- git_config_file *file;+ git_config_backend *file; int ret = GIT_ENOTFOUND;- unsigned int i;-- assert(cfg->files.length);+ size_t i; /* * This loop runs the "wrong" way 'round because we need to@@ -412,7 +496,7 @@ for (i = cfg->files.length; i > 0; --i) { internal = git_vector_get(&cfg->files, i - 1); file = internal->file;- ret = file->get_multivar(file, name, regexp, fn, data);+ ret = file->get_multivar(file, name, regexp, cb, payload); if (ret < 0 && ret != GIT_ENOTFOUND) return ret; }@@ -422,47 +506,57 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value) {+ git_config_backend *file; file_internal *internal;- git_config_file *file;- int ret = GIT_ENOTFOUND;- unsigned int i; - for (i = cfg->files.length; i > 0; --i) {- internal = git_vector_get(&cfg->files, i - 1);- file = internal->file;- ret = file->set_multivar(file, name, regexp, value);- if (ret < 0 && ret != GIT_ENOTFOUND)- return ret;+ internal = git_vector_get(&cfg->files, 0);+ file = internal->file;++ return file->set_multivar(file, name, regexp, value);+}++static int git_config__find_file_to_path(+ char *out, size_t outlen, int (*find)(git_buf *buf))+{+ int error = 0;+ git_buf path = GIT_BUF_INIT;++ if ((error = find(&path)) < 0)+ goto done;++ if (path.size >= outlen) {+ giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path");+ error = GIT_EBUFS;+ goto done; } - return 0;+ git_buf_copy_cstr(out, outlen, &path);++done:+ git_buf_free(&path);+ return error; } int git_config_find_global_r(git_buf *path) {- return git_futils_find_global_file(path, GIT_CONFIG_FILENAME);+ return git_futils_find_global_file(path, GIT_CONFIG_FILENAME_GLOBAL); } int git_config_find_global(char *global_config_path, size_t length) {- git_buf path = GIT_BUF_INIT;- int ret = git_config_find_global_r(&path);-- if (ret < 0) {- git_buf_free(&path);- return ret;- }+ return git_config__find_file_to_path(+ global_config_path, length, git_config_find_global_r);+} - if (path.size >= length) {- git_buf_free(&path);- giterr_set(GITERR_NOMEMORY,- "Path is to long to fit on the given buffer");- return -1;- }+int git_config_find_xdg_r(git_buf *path)+{+ return git_futils_find_xdg_file(path, GIT_CONFIG_FILENAME_XDG);+} - git_buf_copy_cstr(global_config_path, length, &path);- git_buf_free(&path);- return 0;+int git_config_find_xdg(char *xdg_config_path, size_t length)+{+ return git_config__find_file_to_path(+ xdg_config_path, length, git_config_find_xdg_r); } int git_config_find_system_r(git_buf *path)@@ -472,37 +566,244 @@ int git_config_find_system(char *system_config_path, size_t length) {- git_buf path = GIT_BUF_INIT;- int ret = git_config_find_system_r(&path);+ return git_config__find_file_to_path(+ system_config_path, length, git_config_find_system_r);+} - if (ret < 0) {- git_buf_free(&path);- return ret;+int git_config_open_default(git_config **out)+{+ int error;+ git_config *cfg = NULL;+ git_buf buf = GIT_BUF_INIT;++ error = git_config_new(&cfg);++ if (!error && !git_config_find_global_r(&buf))+ error = git_config_add_file_ondisk(cfg, buf.ptr,+ GIT_CONFIG_LEVEL_GLOBAL, 0);++ if (!error && !git_config_find_xdg_r(&buf))+ error = git_config_add_file_ondisk(cfg, buf.ptr,+ GIT_CONFIG_LEVEL_XDG, 0);++ if (!error && !git_config_find_system_r(&buf))+ error = git_config_add_file_ondisk(cfg, buf.ptr,+ GIT_CONFIG_LEVEL_SYSTEM, 0);++ git_buf_free(&buf);++ if (error && cfg) {+ git_config_free(cfg);+ cfg = NULL; } - if (path.size >= length) {- git_buf_free(&path);- giterr_set(GITERR_NOMEMORY,- "Path is to long to fit on the given buffer");- return -1;+ *out = cfg;++ return error;+}++/***********+ * Parsers+ ***********/+int git_config_lookup_map_value(+ int *out,+ const git_cvar_map *maps,+ size_t map_n,+ const char *value)+{+ size_t i;++ if (!value)+ goto fail_parse;++ for (i = 0; i < map_n; ++i) {+ const git_cvar_map *m = maps + i;++ switch (m->cvar_type) {+ case GIT_CVAR_FALSE:+ case GIT_CVAR_TRUE: {+ int bool_val;++ if (git__parse_bool(&bool_val, value) == 0 &&+ bool_val == (int)m->cvar_type) {+ *out = m->map_value;+ return 0;+ }+ break;+ }++ case GIT_CVAR_INT32:+ if (git_config_parse_int32(out, value) == 0)+ return 0;+ break;++ case GIT_CVAR_STRING:+ if (strcasecmp(value, m->str_match) == 0) {+ *out = m->map_value;+ return 0;+ }+ break;+ } } - git_buf_copy_cstr(system_config_path, length, &path);- git_buf_free(&path);+fail_parse:+ giterr_set(GITERR_CONFIG, "Failed to map '%s'", value);+ return -1;+}++int git_config_parse_bool(int *out, const char *value)+{+ if (git__parse_bool(out, value) == 0)+ return 0;++ if (git_config_parse_int32(out, value) == 0) {+ *out = !!(*out);+ return 0;+ }++ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value);+ return -1;+}++int git_config_parse_int64(int64_t *out, const char *value)+{+ const char *num_end;+ int64_t num;++ if (git__strtol64(&num, value, &num_end, 0) < 0)+ goto fail_parse;++ switch (*num_end) {+ case 'g':+ case 'G':+ num *= 1024;+ /* fallthrough */++ case 'm':+ case 'M':+ num *= 1024;+ /* fallthrough */++ case 'k':+ case 'K':+ num *= 1024;++ /* check that that there are no more characters after the+ * given modifier suffix */+ if (num_end[1] != '\0')+ return -1;++ /* fallthrough */++ case '\0':+ *out = num;+ return 0;++ default:+ goto fail_parse;+ }++fail_parse:+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value);+ return -1;+}++int git_config_parse_int32(int32_t *out, const char *value)+{+ int64_t tmp;+ int32_t truncate;++ if (git_config_parse_int64(&tmp, value) < 0)+ goto fail_parse;++ truncate = tmp & 0xFFFFFFFF;+ if (truncate != tmp)+ goto fail_parse;++ *out = truncate; return 0;++fail_parse:+ giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);+ return -1; } -int git_config_open_global(git_config **out)+struct rename_data {+ git_config *config;+ git_buf *name;+ size_t old_len;+ int actual_error;+};++static int rename_config_entries_cb(+ const git_config_entry *entry,+ void *payload) {- int error;- git_buf path = GIT_BUF_INIT;+ int error = 0;+ struct rename_data *data = (struct rename_data *)payload;+ size_t base_len = git_buf_len(data->name); - if ((error = git_config_find_global_r(&path)) < 0)- return error;+ if (base_len > 0 &&+ !(error = git_buf_puts(data->name, entry->name + data->old_len)))+ {+ error = git_config_set_string(+ data->config, git_buf_cstr(data->name), entry->value); - error = git_config_open_ondisk(out, git_buf_cstr(&path));- git_buf_free(&path);+ git_buf_truncate(data->name, base_len);+ } + if (!error)+ error = git_config_delete_entry(data->config, entry->name);++ data->actual_error = error; /* preserve actual error code */+ return error; } +int git_config_rename_section(+ git_repository *repo,+ const char *old_section_name,+ const char *new_section_name)+{+ git_config *config;+ git_buf pattern = GIT_BUF_INIT, replace = GIT_BUF_INIT;+ int error = 0;+ struct rename_data data;++ git_buf_text_puts_escape_regex(&pattern, old_section_name);++ if ((error = git_buf_puts(&pattern, "\\..+")) < 0)+ goto cleanup;++ if ((error = git_repository_config__weakptr(&config, repo)) < 0)+ goto cleanup;++ data.config = config;+ data.name = &replace;+ data.old_len = strlen(old_section_name) + 1;+ data.actual_error = 0;++ if ((error = git_buf_join(&replace, '.', new_section_name, "")) < 0)+ goto cleanup;++ if (new_section_name != NULL &&+ (error = git_config_file_normalize_section(+ replace.ptr, strchr(replace.ptr, '.'))) < 0)+ {+ giterr_set(+ GITERR_CONFIG, "Invalid config section '%s'", new_section_name);+ goto cleanup;+ }++ error = git_config_foreach_match(+ config, git_buf_cstr(&pattern), rename_config_entries_cb, &data);++ if (error == GIT_EUSER)+ error = data.actual_error;++cleanup:+ git_buf_free(&pattern);+ git_buf_free(&replace);++ return error;+}
libgit2/src/config.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -12,9 +12,11 @@ #include "vector.h" #include "repository.h" -#define GIT_CONFIG_FILENAME ".gitconfig"-#define GIT_CONFIG_FILENAME_INREPO "config" #define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"+#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig"+#define GIT_CONFIG_FILENAME_XDG "config"++#define GIT_CONFIG_FILENAME_INREPO "config" #define GIT_CONFIG_FILE_MODE 0666 struct git_config {@@ -23,11 +25,25 @@ }; extern int git_config_find_global_r(git_buf *global_config_path);+extern int git_config_find_xdg_r(git_buf *system_config_path); extern int git_config_find_system_r(git_buf *system_config_path); -extern int git_config_parse_bool(int *out, const char *bool_string);+extern int git_config_rename_section(+ git_repository *repo,+ const char *old_section_name, /* eg "branch.dummy" */+ const char *new_section_name); /* NULL to drop the old section */ -extern int git_config_lookup_map_value(- git_cvar_map *maps, size_t map_n, const char *value, int *out);+/**+ * Create a configuration file backend for ondisk files+ *+ * These are the normal `.gitconfig` files that Core Git+ * processes. Note that you first have to add this file to a+ * configuration object before you can query it for configuration+ * variables.+ *+ * @param out the new backend+ * @param path where the config file is located+ */+extern int git_config_file__ondisk(struct git_config_backend **out, const char *path); #endif
libgit2/src/config_cache.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -24,7 +24,7 @@ * core.eol * Sets the line ending type to use in the working directory for * files that have the text property set. Alternatives are lf, crlf- * and native, which uses the platform’s native line ending. The default+ * and native, which uses the platform's native line ending. The default * value is native. See gitattributes(5) for more information on * end-of-line conversion. */
libgit2/src/config_file.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,6 +10,7 @@ #include "fileops.h" #include "filebuf.h" #include "buffer.h"+#include "buf_text.h" #include "git2/config.h" #include "git2/types.h" #include "strmap.h"@@ -22,15 +23,9 @@ typedef struct cvar_t { struct cvar_t *next;- char *key; /* TODO: we might be able to get rid of this */- char *value;+ git_config_entry *entry; } cvar_t; -typedef struct {- struct cvar_t *head;- struct cvar_t *tail;-} cvar_t_list;- #define CVAR_LIST_HEAD(list) ((list)->head) #define CVAR_LIST_TAIL(list) ((list)->tail)@@ -70,7 +65,7 @@ (iter) = (tmp)) typedef struct {- git_config_file parent;+ git_config_backend parent; git_strmap *values; @@ -81,12 +76,17 @@ int eof; } reader; - char *file_path;+ char *file_path;+ time_t file_mtime;+ size_t file_size;++ unsigned int level; } diskfile_backend; -static int config_parse(diskfile_backend *cfg_file);+static int config_parse(diskfile_backend *cfg_file, unsigned int level); static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value); static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);+static char *escape_value(const char *ptr); static void set_parse_error(diskfile_backend *backend, int col, const char *error_str) {@@ -99,11 +99,35 @@ if (var == NULL) return; - git__free(var->key);- git__free(var->value);+ git__free((char*)var->entry->name);+ git__free((char *)var->entry->value);+ git__free(var->entry); git__free(var); } +int git_config_file_normalize_section(char *start, char *end)+{+ char *scan;++ if (start == end)+ return GIT_EINVALIDSPEC;++ /* Validate and downcase range */+ for (scan = start; *scan; ++scan) {+ if (end && scan >= end)+ break;+ if (isalnum(*scan))+ *scan = tolower(*scan);+ else if (*scan != '-' || scan == start)+ return GIT_EINVALIDSPEC;+ }++ if (scan == start)+ return GIT_EINVALIDSPEC;++ return 0;+}+ /* Take something the user gave us and make it nice for our hash function */ static int normalize_name(const char *in, char **out) {@@ -117,19 +141,26 @@ fdot = strchr(name, '.'); ldot = strrchr(name, '.'); - if (fdot == NULL || ldot == NULL) {- git__free(name);- giterr_set(GITERR_CONFIG,- "Invalid variable name: '%s'", in);- return -1;- }+ if (fdot == NULL || fdot == name || ldot == NULL || !ldot[1])+ goto invalid; - /* Downcase up to the first dot and after the last one */- git__strntolower(name, fdot - name);- git__strtolower(ldot);+ /* Validate and downcase up to first dot and after last dot */+ if (git_config_file_normalize_section(name, fdot) < 0 ||+ git_config_file_normalize_section(ldot + 1, NULL) < 0)+ goto invalid; + /* If there is a middle range, make sure it doesn't have newlines */+ while (fdot < ldot)+ if (*fdot++ == '\n')+ goto invalid;+ *out = name; return 0;++invalid:+ git__free(name);+ giterr_set(GITERR_CONFIG, "Invalid config item name '%s'", in);+ return GIT_EINVALIDSPEC; } static void free_vars(git_strmap *values)@@ -149,34 +180,62 @@ git_strmap_free(values); } -static int config_open(git_config_file *cfg)+static int config_open(git_config_backend *cfg, unsigned int level) { int res; diskfile_backend *b = (diskfile_backend *)cfg; + b->level = level;+ b->values = git_strmap_alloc(); GITERR_CHECK_ALLOC(b->values); git_buf_init(&b->reader.buffer, 0);- res = git_futils_readbuffer(&b->reader.buffer, b->file_path);+ res = git_futils_readbuffer_updated(+ &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL); /* It's fine if the file doesn't exist */ if (res == GIT_ENOTFOUND) return 0; - if (res < 0 || config_parse(b) < 0) {+ if (res < 0 || (res = config_parse(b, level)) < 0) { free_vars(b->values); b->values = NULL;- git_buf_free(&b->reader.buffer);- return -1; } git_buf_free(&b->reader.buffer);- return 0;+ return res; } -static void backend_free(git_config_file *_backend)+static int config_refresh(git_config_backend *cfg) {+ int res, updated = 0;+ diskfile_backend *b = (diskfile_backend *)cfg;+ git_strmap *old_values;++ res = git_futils_readbuffer_updated(+ &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated);+ if (res < 0 || !updated)+ return (res == GIT_ENOTFOUND) ? 0 : res;++ /* need to reload - store old values and prep for reload */+ old_values = b->values;+ b->values = git_strmap_alloc();+ GITERR_CHECK_ALLOC(b->values);++ if ((res = config_parse(b, b->level)) < 0) {+ free_vars(b->values);+ b->values = old_values;+ } else {+ free_vars(old_values);+ }++ git_buf_free(&b->reader.buffer);+ return res;+}++static void backend_free(git_config_backend *_backend)+{ diskfile_backend *backend = (diskfile_backend *)_backend; if (backend == NULL)@@ -187,37 +246,63 @@ git__free(backend); } -static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data)+static int file_foreach(+ git_config_backend *backend,+ const char *regexp,+ int (*fn)(const git_config_entry *, void *),+ void *data) { diskfile_backend *b = (diskfile_backend *)backend;- cvar_t *var;+ cvar_t *var, *next_var; const char *key;+ regex_t regex;+ int result = 0; if (!b->values) return 0; + if (regexp != NULL) {+ if ((result = regcomp(®ex, regexp, REG_EXTENDED)) < 0) {+ giterr_set_regex(®ex, result);+ regfree(®ex);+ return -1;+ }+ }+ git_strmap_foreach(b->values, key, var,- do {- if (fn(key, var->value, data) < 0)- break;+ for (; var != NULL; var = next_var) {+ next_var = CVAR_LIST_NEXT(var); - var = CVAR_LIST_NEXT(var);- } while (var != NULL);+ /* skip non-matching keys if regexp was provided */+ if (regexp && regexec(®ex, key, 0, NULL, 0) != 0)+ continue;++ /* abort iterator on non-zero return value */+ if (fn(var->entry, data)) {+ giterr_clear();+ result = GIT_EUSER;+ goto cleanup;+ }+ } ); - return 0;+cleanup:+ if (regexp != NULL)+ regfree(®ex);++ return result; } -static int config_set(git_config_file *cfg, const char *name, const char *value)+static int config_set(git_config_backend *cfg, const char *name, const char *value) { cvar_t *var = NULL, *old_var; diskfile_backend *b = (diskfile_backend *)cfg;- char *key;+ char *key, *esc_value = NULL; khiter_t pos;- int rval;+ int rval, ret; - if (normalize_name(name, &key) < 0)- return -1;+ if ((rval = normalize_name(name, &key)) < 0)+ return rval; /* * Try to find it in the existing values and update it if it@@ -229,40 +314,57 @@ char *tmp = NULL; git__free(key);+ if (existing->next != NULL) { giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set"); return -1; } + /* don't update if old and new values already match */+ if ((!existing->entry->value && !value) ||+ (existing->entry->value && value && !strcmp(existing->entry->value, value)))+ return 0;+ if (value) { tmp = git__strdup(value); GITERR_CHECK_ALLOC(tmp);+ esc_value = escape_value(value);+ GITERR_CHECK_ALLOC(esc_value); } - git__free(existing->value);- existing->value = tmp;+ git__free((void *)existing->entry->value);+ existing->entry->value = tmp; - return config_write(b, existing->key, NULL, value);+ ret = config_write(b, existing->entry->name, NULL, esc_value);++ git__free(esc_value);+ return ret; } var = git__malloc(sizeof(cvar_t)); GITERR_CHECK_ALLOC(var);- memset(var, 0x0, sizeof(cvar_t));+ var->entry = git__malloc(sizeof(git_config_entry));+ GITERR_CHECK_ALLOC(var->entry);+ memset(var->entry, 0x0, sizeof(git_config_entry)); - var->key = key;- var->value = NULL;+ var->entry->name = key;+ var->entry->value = NULL; if (value) {- var->value = git__strdup(value);- GITERR_CHECK_ALLOC(var->value);+ var->entry->value = git__strdup(value);+ GITERR_CHECK_ALLOC(var->entry->value);+ esc_value = escape_value(value);+ GITERR_CHECK_ALLOC(esc_value); } - if (config_write(b, key, NULL, value) < 0) {+ if (config_write(b, key, NULL, esc_value) < 0) {+ git__free(esc_value); cvar_free(var); return -1; } + git__free(esc_value); git_strmap_insert2(b->values, key, var, old_var, rval); if (rval < 0) return -1;@@ -275,14 +377,15 @@ /* * Internal function that actually gets the value in string form */-static int config_get(git_config_file *cfg, const char *name, const char **out)+static int config_get(const git_config_backend *cfg, const char *name, const git_config_entry **out) { diskfile_backend *b = (diskfile_backend *)cfg; char *key; khiter_t pos;+ int error; - if (normalize_name(name, &key) < 0)- return -1;+ if ((error = normalize_name(name, &key)) < 0)+ return error; pos = git_strmap_lookup_index(b->values, key); git__free(key);@@ -291,25 +394,26 @@ if (!git_strmap_valid_index(b->values, pos)) return GIT_ENOTFOUND; - *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->value;+ *out = ((cvar_t *)git_strmap_value_at(b->values, pos))->entry; return 0; } static int config_get_multivar(- git_config_file *cfg,+ git_config_backend *cfg, const char *name, const char *regex_str,- int (*fn)(const char *, void *),+ int (*fn)(const git_config_entry *, void *), void *data) { cvar_t *var; diskfile_backend *b = (diskfile_backend *)cfg; char *key; khiter_t pos;+ int error; - if (normalize_name(name, &key) < 0)- return -1;+ if ((error = normalize_name(name, &key)) < 0)+ return error; pos = git_strmap_lookup_index(b->values, key); git__free(key);@@ -327,16 +431,17 @@ result = regcomp(®ex, regex_str, REG_EXTENDED); if (result < 0) { giterr_set_regex(®ex, result);+ regfree(®ex); return -1; } /* and throw the callback only on the variables that * match the regex */ do {- if (regexec(®ex, var->value, 0, NULL, 0) == 0) {+ if (regexec(®ex, var->entry->value, 0, NULL, 0) == 0) { /* early termination by the user is not an error; * just break and return successfully */- if (fn(var->value, data) < 0)+ if (fn(var->entry, data) < 0) break; } @@ -348,7 +453,7 @@ do { /* early termination by the user is not an error; * just break and return successfully */- if (fn(var->value, data) < 0)+ if (fn(var->entry, data) < 0) break; var = var->next;@@ -359,7 +464,7 @@ } static int config_set_multivar(- git_config_file *cfg, const char *name, const char *regexp, const char *value)+ git_config_backend *cfg, const char *name, const char *regexp, const char *value) { int replaced = 0; cvar_t *var, *newvar;@@ -371,8 +476,8 @@ assert(regexp); - if (normalize_name(name, &key) < 0)- return -1;+ if ((result = normalize_name(name, &key)) < 0)+ return result; pos = git_strmap_lookup_index(b->values, key); if (!git_strmap_valid_index(b->values, pos)) {@@ -386,16 +491,17 @@ if (result < 0) { git__free(key); giterr_set_regex(&preg, result);+ regfree(&preg); return -1; } for (;;) {- if (regexec(&preg, var->value, 0, NULL, 0) == 0) {+ if (regexec(&preg, var->entry->value, 0, NULL, 0) == 0) { char *tmp = git__strdup(value); GITERR_CHECK_ALLOC(tmp); - git__free(var->value);- var->value = tmp;+ git__free((void *)var->entry->value);+ var->entry->value = tmp; replaced = 1; } @@ -409,15 +515,19 @@ if (!replaced) { newvar = git__malloc(sizeof(cvar_t)); GITERR_CHECK_ALLOC(newvar);- memset(newvar, 0x0, sizeof(cvar_t));+ newvar->entry = git__malloc(sizeof(git_config_entry));+ GITERR_CHECK_ALLOC(newvar->entry);+ memset(newvar->entry, 0x0, sizeof(git_config_entry)); - newvar->key = git__strdup(var->key);- GITERR_CHECK_ALLOC(newvar->key);+ newvar->entry->name = git__strdup(var->entry->name);+ GITERR_CHECK_ALLOC(newvar->entry->name); - newvar->value = git__strdup(value);- GITERR_CHECK_ALLOC(newvar->value);+ newvar->entry->value = git__strdup(value);+ GITERR_CHECK_ALLOC(newvar->entry->value); + newvar->entry->level = var->entry->level;+ var->next = newvar; } @@ -429,7 +539,7 @@ return result; } -static int config_delete(git_config_file *cfg, const char *name)+static int config_delete(git_config_backend *cfg, const char *name) { cvar_t *var; diskfile_backend *b = (diskfile_backend *)cfg;@@ -437,14 +547,16 @@ int result; khiter_t pos; - if (normalize_name(name, &key) < 0)- return -1;+ if ((result = normalize_name(name, &key)) < 0)+ return result; pos = git_strmap_lookup_index(b->values, key); git__free(key); - if (!git_strmap_valid_index(b->values, pos))+ if (!git_strmap_valid_index(b->values, pos)) {+ giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name); return GIT_ENOTFOUND;+ } var = git_strmap_value_at(b->values, pos); @@ -455,20 +567,20 @@ git_strmap_delete_at(b->values, pos); - result = config_write(b, var->key, NULL, NULL);+ result = config_write(b, var->entry->name, NULL, NULL); cvar_free(var); return result; } -int git_config_file__ondisk(git_config_file **out, const char *path)+int git_config_file__ondisk(git_config_backend **out, const char *path) { diskfile_backend *backend; - backend = git__malloc(sizeof(diskfile_backend));+ backend = git__calloc(1, sizeof(diskfile_backend)); GITERR_CHECK_ALLOC(backend); - memset(backend, 0x0, sizeof(diskfile_backend));+ backend->parent.version = GIT_CONFIG_BACKEND_VERSION; backend->file_path = git__strdup(path); GITERR_CHECK_ALLOC(backend->file_path);@@ -480,9 +592,10 @@ backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; backend->parent.foreach = file_foreach;+ backend->parent.refresh = config_refresh; backend->parent.free = backend_free; - *out = (git_config_file *)backend;+ *out = (git_config_backend *)backend; return 0; }@@ -774,17 +887,14 @@ static int skip_bom(diskfile_backend *cfg) {- static const char utf8_bom[] = "\xef\xbb\xbf";-- if (cfg->reader.buffer.size < sizeof(utf8_bom))- return 0;+ git_bom_t bom;+ int bom_offset = git_buf_text_detect_bom(&bom,+ &cfg->reader.buffer, cfg->reader.read_ptr - cfg->reader.buffer.ptr); - if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0)- cfg->reader.read_ptr += sizeof(utf8_bom);+ if (bom == GIT_BOM_UTF8)+ cfg->reader.read_ptr += bom_offset; - /* TODO: the reference implementation does pretty stupid- shit with the BoM- */+ /* TODO: reference implementation is pretty stupid with BoM */ return 0; }@@ -844,7 +954,7 @@ } /* skip any space at the end */- if (git__isspace(ptr[-1])) {+ if (ptr > line && git__isspace(ptr[-1])) { ptr--; } ptr[0] = '\0';@@ -852,7 +962,7 @@ return quote_count; } -static int config_parse(diskfile_backend *cfg_file)+static int config_parse(diskfile_backend *cfg_file, unsigned int level) { int c; char *current_section = NULL;@@ -900,8 +1010,10 @@ var = git__malloc(sizeof(cvar_t)); GITERR_CHECK_ALLOC(var);- memset(var, 0x0, sizeof(cvar_t));+ var->entry = git__malloc(sizeof(git_config_entry));+ GITERR_CHECK_ALLOC(var->entry);+ memset(var->entry, 0x0, sizeof(git_config_entry)); git__strtolower(var_name); git_buf_printf(&buf, "%s.%s", current_section, var_name);@@ -910,13 +1022,14 @@ if (git_buf_oom(&buf)) return -1; - var->key = git_buf_detach(&buf);- var->value = var_value;+ var->entry->name = git_buf_detach(&buf);+ var->entry->value = var_value;+ var->entry->level = level; /* Add or append the new config option */- pos = git_strmap_lookup_index(cfg_file->values, var->key);+ pos = git_strmap_lookup_index(cfg_file->values, var->entry->name); if (!git_strmap_valid_index(cfg_file->values, pos)) {- git_strmap_insert(cfg_file->values, var->key, var, result);+ git_strmap_insert(cfg_file->values, var->entry->name, var, result); if (result < 0) break; result = 0;@@ -948,9 +1061,12 @@ if (dot == NULL) { git_buf_puts(&buf, key); } else {+ char *escaped; git_buf_put(&buf, key, dot - key);- /* TODO: escape */- git_buf_printf(&buf, " \"%s\"", dot + 1);+ escaped = escape_value(dot + 1);+ GITERR_CHECK_ALLOC(escaped);+ git_buf_printf(&buf, " \"%s\"", escaped);+ git__free(escaped); } git_buf_puts(&buf, "]\n"); @@ -1133,6 +1249,10 @@ goto rewrite_fail; } + /* If we are here, there is at least a section line */+ if (cfg->reader.buffer.size > 0 && *(cfg->reader.buffer.ptr + cfg->reader.buffer.size - 1) != '\n')+ git_filebuf_write(&file, "\n", 1);+ git_filebuf_printf(&file, "\t%s = %s\n", name, value); } }@@ -1140,8 +1260,12 @@ git__free(section); git__free(current_section); + /* refresh stats - if this errors, then commit will error too */+ (void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file);+ result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); git_buf_free(&cfg->reader.buffer);+ return result; rewrite_fail:@@ -1153,13 +1277,44 @@ return -1; } +static const char *escapes = "ntb\"\\";+static const char *escaped = "\n\t\b\"\\";++/* Escape the values to write them to the file */+static char *escape_value(const char *ptr)+{+ git_buf buf = GIT_BUF_INIT;+ size_t len;+ const char *esc;++ assert(ptr);++ len = strlen(ptr);+ git_buf_grow(&buf, len);++ while (*ptr != '\0') {+ if ((esc = strchr(escaped, *ptr)) != NULL) {+ git_buf_putc(&buf, '\\');+ git_buf_putc(&buf, escapes[esc - escaped]);+ } else {+ git_buf_putc(&buf, *ptr);+ }+ ptr++;+ }++ if (git_buf_oom(&buf)) {+ git_buf_free(&buf);+ return NULL;+ }++ return git_buf_detach(&buf);+}+ /* '\"' -> '"' etc */ static char *fixup_line(const char *ptr, int quote_count) { char *str = git__malloc(strlen(ptr) + 1); char *out = str, *esc;- const char *escapes = "ntb\"\\";- const char *escaped = "\n\t\b\"\\"; if (str == NULL) return NULL;@@ -1196,8 +1351,15 @@ static int is_multiline_var(const char *str) {+ int count = 0; const char *end = str + strlen(str);- return (end > str) && (end[-1] == '\\');+ while (end > str && end[-1] == '\\') {+ count++;+ end--;+ }++ /* An odd number means last backslash wasn't escaped, so it's multiline */+ return (end > str) && (count & 1); } static int parse_multiline_variable(diskfile_backend *cfg, git_buf *value, int in_quotes)@@ -1272,10 +1434,8 @@ else value_start = var_end + 1; - if (git__isspace(var_end[-1])) {- do var_end--;- while (git__isspace(var_end[0]));- }+ do var_end--;+ while (var_end>line && git__isspace(*var_end)); *var_name = git__strndup(line, var_end - line + 1); GITERR_CHECK_ALLOC(*var_name);@@ -1309,8 +1469,10 @@ else if (value_start[0] != '\0') { *var_value = fixup_line(value_start, 0); GITERR_CHECK_ALLOC(*var_value);+ } else { /* equals sign but missing rhs */+ *var_value = git__strdup("");+ GITERR_CHECK_ALLOC(*var_value); }- } git__free(line);
libgit2/src/config_file.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -9,23 +9,52 @@ #include "git2/config.h" -GIT_INLINE(int) git_config_file_open(git_config_file *cfg)+GIT_INLINE(int) git_config_file_open(git_config_backend *cfg, unsigned int level) {- return cfg->open(cfg);+ return cfg->open(cfg, level); } -GIT_INLINE(void) git_config_file_free(git_config_file *cfg)+GIT_INLINE(void) git_config_file_free(git_config_backend *cfg) { cfg->free(cfg); } +GIT_INLINE(int) git_config_file_get_string(+ const git_config_entry **out, git_config_backend *cfg, const char *name)+{+ return cfg->get(cfg, name, out);+}++GIT_INLINE(int) git_config_file_set_string(+ git_config_backend *cfg, const char *name, const char *value)+{+ return cfg->set(cfg, name, value);+}++GIT_INLINE(int) git_config_file_delete(+ git_config_backend *cfg, const char *name)+{+ return cfg->del(cfg, name);+}+ GIT_INLINE(int) git_config_file_foreach(- git_config_file *cfg,- int (*fn)(const char *key, const char *value, void *data),+ git_config_backend *cfg,+ int (*fn)(const git_config_entry *entry, void *data), void *data) {- return cfg->foreach(cfg, fn, data);+ return cfg->foreach(cfg, NULL, fn, data); }++GIT_INLINE(int) git_config_file_foreach_match(+ git_config_backend *cfg,+ const char *regexp,+ int (*fn)(const git_config_entry *entry, void *data),+ void *data)+{+ return cfg->foreach(cfg, regexp, fn, data);+}++extern int git_config_file_normalize_section(char *start, char *end); #endif
libgit2/src/crlf.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -9,9 +9,10 @@ #include "fileops.h" #include "hash.h" #include "filter.h"+#include "buf_text.h" #include "repository.h"- #include "git2/attr.h"+#include "git2/blob.h" struct crlf_attrs { int crlf_action;@@ -21,6 +22,8 @@ struct crlf_filter { git_filter f; struct crlf_attrs attrs;+ git_repository *repo;+ char path[GIT_FLEX_ARRAY]; }; static int check_crlf(const char *value)@@ -103,36 +106,46 @@ return -1; } -static int drop_crlf(git_buf *dest, const git_buf *source)+static int has_cr_in_index(git_filter *self) {- const char *scan = source->ptr, *next;- const char *scan_end = git_buf_cstr(source) + git_buf_len(source);+ struct crlf_filter *filter = (struct crlf_filter *)self;+ git_index *index;+ const git_index_entry *entry;+ git_blob *blob;+ const void *blobcontent;+ git_off_t blobsize;+ bool found_cr; - /* Main scan loop. Find the next carriage return and copy the- * whole chunk up to that point to the destination buffer.- */- while ((next = memchr(scan, '\r', scan_end - scan)) != NULL) {- /* copy input up to \r */- if (next > scan)- git_buf_put(dest, scan, next - scan);+ if (git_repository_index__weakptr(&index, filter->repo) < 0) {+ giterr_clear();+ return false;+ } - /* Do not drop \r unless it is followed by \n */- if (*(next + 1) != '\n')- git_buf_putc(dest, '\r');+ if (!(entry = git_index_get_bypath(index, filter->path, 0)) &&+ !(entry = git_index_get_bypath(index, filter->path, 1)))+ return false; - scan = next + 1;- }+ if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */+ return true; - /* If there was no \r, then tell the library to skip this filter */- if (scan == source->ptr)- return -1;+ if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0)+ return false; - /* Copy remaining input into dest */- git_buf_put(dest, scan, scan_end - scan);- return 0;+ blobcontent = git_blob_rawcontent(blob);+ blobsize = git_blob_rawsize(blob);+ if (!git__is_sizet(blobsize))+ blobsize = (size_t)-1;++ found_cr = (blobcontent != NULL &&+ blobsize > 0 &&+ memchr(blobcontent, '\r', (size_t)blobsize) != NULL);++ git_blob_free(blob);+ return found_cr; } -static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source)+static int crlf_apply_to_odb(+ git_filter *self, git_buf *dest, const git_buf *source) { struct crlf_filter *filter = (struct crlf_filter *)self; @@ -148,9 +161,12 @@ if (filter->attrs.crlf_action == GIT_CRLF_AUTO || filter->attrs.crlf_action == GIT_CRLF_GUESS) { - git_text_stats stats;- git_text_gather_stats(&stats, source);+ git_buf_text_stats stats; + /* Check heuristics for binary vs text... */+ if (git_buf_text_gather_stats(&stats, source, false))+ return -1;+ /* * We're currently not going to even try to convert stuff * that has bare CR characters. Does anybody do that crazy@@ -159,35 +175,94 @@ if (stats.cr != stats.crlf) return -1; - /*- * And add some heuristics for binary vs text, of course...- */- if (git_text_is_binary(&stats))- return -1;--#if 0- if (crlf_action == CRLF_GUESS) {+ if (filter->attrs.crlf_action == GIT_CRLF_GUESS) { /* * If the file in the index has any CR in it, do not convert. * This is the new safer autocrlf handling. */- if (has_cr_in_index(path))- return 0;+ if (has_cr_in_index(self))+ return -1; }-#endif if (!stats.cr) return -1; } /* Actually drop the carriage returns */- return drop_crlf(dest, source);+ return git_buf_text_crlf_to_lf(dest, source); } -int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path)+static const char *line_ending(struct crlf_filter *filter) {+ switch (filter->attrs.crlf_action) {+ case GIT_CRLF_BINARY:+ case GIT_CRLF_INPUT:+ return "\n";++ case GIT_CRLF_CRLF:+ return "\r\n";++ case GIT_CRLF_AUTO:+ case GIT_CRLF_TEXT:+ case GIT_CRLF_GUESS:+ break;++ default:+ goto line_ending_error;+ }++ switch (filter->attrs.eol) {+ case GIT_EOL_UNSET:+ return GIT_EOL_NATIVE == GIT_EOL_CRLF+ ? "\r\n"+ : "\n";++ case GIT_EOL_CRLF:+ return "\r\n";++ case GIT_EOL_LF:+ return "\n";++ default:+ goto line_ending_error;+ }++line_ending_error:+ giterr_set(GITERR_INVALID, "Invalid input to line ending filter");+ return NULL;+}++static int crlf_apply_to_workdir(+ git_filter *self, git_buf *dest, const git_buf *source)+{+ struct crlf_filter *filter = (struct crlf_filter *)self;+ const char *workdir_ending = NULL;++ assert(self && dest && source);++ /* Empty file? Nothing to do. */+ if (git_buf_len(source) == 0)+ return -1;++ /* Determine proper line ending */+ workdir_ending = line_ending(filter);+ if (!workdir_ending)+ return -1;+ if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */+ return -1;++ /* for now, only lf->crlf conversion is supported here */+ assert(!strcmp("\r\n", workdir_ending));+ return git_buf_text_lf_to_crlf(dest, source);+}++static int find_and_add_filter(+ git_vector *filters, git_repository *repo, const char *path,+ int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source))+{ struct crlf_attrs ca; struct crlf_filter *filter;+ size_t pathlen; int error; /* Load gitattributes for the path */@@ -196,7 +271,7 @@ /* * Use the core Git logic to see if we should perform CRLF for this file- * based on its attributes & the value of `core.auto_crlf`+ * based on its attributes & the value of `core.autocrlf` */ ca.crlf_action = crlf_input_action(&ca); @@ -206,8 +281,7 @@ if (ca.crlf_action == GIT_CRLF_GUESS) { int auto_crlf; - if ((error = git_repository__cvar(- &auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0)+ if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) return error; if (auto_crlf == GIT_AUTO_CRLF_FALSE)@@ -216,13 +290,27 @@ /* If we're good, we create a new filter object and push it * into the filters array */- filter = git__malloc(sizeof(struct crlf_filter));+ pathlen = strlen(path);+ filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1); GITERR_CHECK_ALLOC(filter); - filter->f.apply = &crlf_apply_to_odb;+ filter->f.apply = apply; filter->f.do_free = NULL; memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs));+ filter->repo = repo;+ memcpy(filter->path, path, pathlen + 1); return git_vector_insert(filters, filter); } +int git_filter_add__crlf_to_odb(+ git_vector *filters, git_repository *repo, const char *path)+{+ return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb);+}++int git_filter_add__crlf_to_workdir(+ git_vector *filters, git_repository *repo, const char *path)+{+ return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir);+}
+ libgit2/src/date.c view
@@ -0,0 +1,876 @@+/*+ * GIT - The information manager from hell+ *+ * Copyright (C) Linus Torvalds, 2005+ */++#include "common.h"++#ifndef GIT_WIN32+#include <sys/time.h>+#endif++#include "util.h"+#include "cache.h"+#include "posix.h"++#include <ctype.h>+#include <time.h>++typedef enum {+ DATE_NORMAL = 0,+ DATE_RELATIVE,+ DATE_SHORT,+ DATE_LOCAL,+ DATE_ISO8601,+ DATE_RFC2822,+ DATE_RAW+} date_mode;++/*+ * This is like mktime, but without normalization of tm_wday and tm_yday.+ */+static git_time_t tm_to_time_t(const 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--;+ if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)+ return -1;+ 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"+};++++/*+ * 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) */+ { "Z", 0, 0, }, /* Zulu, alias for UTC */++ { "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 */+ { "EEST", +2, 1, }, /* Eastern European Daylight */+ { "WAST", +7, 0, }, /* West Australian Standard */+ { "WADT", +7, 1, }, /* West Australian Daylight */+ { "CCT", +8, 0, }, /* China Coast */+ { "JST", +9, 0, }, /* Japan Standard */+ { "EAST", +10, 0, }, /* Eastern Australian Standard */+ { "EADT", +10, 1, }, /* Eastern Australian Daylight */+ { "GST", +10, 0, }, /* Guam Standard */+ { "NZT", +12, 0, }, /* New Zealand */+ { "NZST", +12, 0, }, /* New Zealand Standard */+ { "NZDT", +12, 1, }, /* New Zealand Daylight */+ { "IDLE", +12, 0, }, /* International Date Line East */+};++static size_t match_string(const char *date, const char *str)+{+ size_t 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 size_t match_alpha(const char *date, struct tm *tm, int *offset)+{+ unsigned int i;++ for (i = 0; i < 12; i++) {+ size_t match = match_string(date, month_names[i]);+ if (match >= 3) {+ tm->tm_mon = i;+ return match;+ }+ }++ for (i = 0; i < 7; i++) {+ size_t match = match_string(date, weekday_names[i]);+ if (match >= 3) {+ tm->tm_wday = i;+ return match;+ }+ }++ for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {+ size_t match = match_string(date, timezone_names[i].name);+ if (match >= 3 || match == strlen(timezone_names[i].name)) {+ 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) {+ tm->tm_hour = (tm->tm_hour % 12) + 12;+ return 2;+ }++ if (match_string(date, "AM") == 2) {+ tm->tm_hour = (tm->tm_hour % 12) + 0;+ return 2;+ }++ /* BAD */+ return skip_alpha(date);+}++static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)+{+ if (month > 0 && month < 13 && day > 0 && day < 32) {+ struct tm check = *tm;+ struct tm *r = (now_tm ? &check : tm);+ time_t specified;++ r->tm_mon = month - 1;+ r->tm_mday = day;+ if (year == -1) {+ if (!now_tm)+ return 1;+ r->tm_year = now_tm->tm_year;+ }+ else if (year >= 1970 && year < 2100)+ r->tm_year = year - 1900;+ else if (year > 70 && year < 100)+ r->tm_year = year;+ else if (year < 38)+ r->tm_year = year + 100;+ else+ return 0;+ if (!now_tm)+ return 1;++ specified = tm_to_time_t(r);++ /* Be it commit time or author time, it does not make+ * sense to specify timestamp way into the future. Make+ * sure it is not later than ten days from now...+ */+ if (now + 10*24*3600 < specified)+ return 0;+ tm->tm_mon = r->tm_mon;+ tm->tm_mday = r->tm_mday;+ if (year != -1)+ tm->tm_year = r->tm_year;+ return 1;+ }+ return 0;+}++static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)+{+ time_t now;+ struct tm now_tm;+ struct tm *refuse_future;+ 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 '/':+ case '.':+ now = time(NULL);+ refuse_future = NULL;+ if (p_gmtime_r(&now, &now_tm))+ refuse_future = &now_tm;++ if (num > 70) {+ /* yyyy-mm-dd? */+ if (is_date(num, num2, num3, refuse_future, now, tm))+ break;+ /* yyyy-dd-mm? */+ if (is_date(num, num3, num2, refuse_future, now, tm))+ break;+ }+ /* Our eastern European friends say dd.mm.yy[yy]+ * is the norm there, so giving precedence to+ * mm/dd/yy[yy] form only when separator is not '.'+ */+ if (c != '.' &&+ is_date(num3, num, num2, refuse_future, now, tm))+ break;+ /* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */+ if (is_date(num3, num2, num, refuse_future, now, tm))+ break;+ /* Funny European mm.dd.yy */+ if (c == '.' &&+ is_date(num3, num, num2, refuse_future, now, tm))+ break;+ return 0;+ }+ return end - date;+}++/*+ * Have we filled in any part of the time/date yet?+ * We just do a binary 'and' to see if the sign bit+ * is set in all the values.+ */+static int nodate(struct tm *tm)+{+ return (tm->tm_year &+ tm->tm_mon &+ tm->tm_mday &+ tm->tm_hour &+ tm->tm_min &+ tm->tm_sec) < 0;+}++/*+ * We've seen a digit. Time? Year? Date?+ */+static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)+{+ size_t n;+ char *end;+ unsigned long num;++ num = strtoul(date, &end, 10);++ /*+ * Seconds since 1970? We trigger on that for any numbers with+ * more than 8 digits. This is because we don't want to rule out+ * numbers like 20070606 as a YYYYMMDD date.+ */+ if (num >= 100000000 && nodate(tm)) {+ time_t time = num;+ if (p_gmtime_r(&time, tm)) {+ *tm_gmt = 1;+ return end - date;+ }+ }++ /*+ * Check for special formats: num[-.:/]num[same]num+ */+ switch (*end) {+ case ':':+ case '.':+ case '/':+ case '-':+ if (isdigit(end[1])) {+ size_t 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 <= 1400 && *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;+ }++ /*+ * Ignore lots of numerals. We took care of 4-digit years above.+ * Days or months must be one or two digits.+ */+ if (n > 2)+ return n;++ /*+ * NOTE! We will give precedence to day-of-month over month or+ * year numbers 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 < 13 && tm->tm_mon < 0)+ tm->tm_mon = num-1;++ return n;+}++static size_t match_tz(const char *date, int *offp)+{+ char *end;+ int hour = strtoul(date + 1, &end, 10);+ size_t n = end - (date + 1);+ int min = 0;++ if (n == 4) {+ /* hhmm */+ min = hour % 100;+ hour = hour / 100;+ } else if (n != 2) {+ min = 99; /* random stuff */+ } else if (*end == ':') {+ /* hh:mm? */+ min = strtoul(end + 1, &end, 10);+ if (end - (date + 1) != 5)+ min = 99; /* random stuff */+ } /* otherwise we parsed "hh" */++ /*+ * Don't accept any random stuff. Even though some places have+ * offset larger than 12 hours (e.g. Pacific/Kiritimati is at+ * UTC+14), there is something wrong if hour part is much+ * larger than that. We might also want to check that the+ * minutes are divisible by 15 or something too. (Offset of+ * Kathmandu, Nepal is UTC+5:45)+ */+ if (min < 60 && hour < 24) {+ int offset = hour * 60 + min;+ if (*date == '-')+ offset = -offset;+ *offp = offset;+ }+ return end - date;+}++/*+ * Parse a string like "0 +0000" as ancient timestamp near epoch, but+ * only when it appears not as part of any other string.+ */+static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset)+{+ char *end;+ unsigned long stamp;+ int ofs;++ if (*date < '0' || '9' <= *date)+ return -1;+ stamp = strtoul(date, &end, 10);+ if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))+ return -1;+ date = end + 2;+ ofs = strtol(date, &end, 10);+ if ((*end != '\0' && (*end != '\n')) || end != date + 4)+ return -1;+ ofs = (ofs / 100) * 60 + (ofs % 100);+ if (date[-1] == '-')+ ofs = -ofs;+ *timestamp = stamp;+ *offset = ofs;+ return 0;+}++/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822+ (i.e. English) day/month names, and it doesn't work correctly with %z. */+static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset)+{+ struct tm tm;+ int tm_gmt;+ git_time_t dummy_timestamp;+ int dummy_offset;++ if (!timestamp)+ timestamp = &dummy_timestamp;+ if (!offset)+ offset = &dummy_offset;++ memset(&tm, 0, sizeof(tm));+ tm.tm_year = -1;+ tm.tm_mon = -1;+ tm.tm_mday = -1;+ tm.tm_isdst = -1;+ tm.tm_hour = -1;+ tm.tm_min = -1;+ tm.tm_sec = -1;+ *offset = -1;+ tm_gmt = 0;++ if (*date == '@' &&+ !match_object_header_date(date + 1, timestamp, offset))+ return 0; /* success */+ for (;;) {+ size_t 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 */+ match = 1;+ }++ date += match;+ }++ /* mktime uses local timezone */+ *timestamp = tm_to_time_t(&tm);+ if (*offset == -1)+ *offset = (int)((time_t)*timestamp - mktime(&tm)) / 60;++ if (*timestamp == (git_time_t)-1)+ return -1;++ if (!tm_gmt)+ *timestamp -= *offset * 60;+ return 0; /* success */+}+++/*+ * Relative time update (eg "2 days ago"). If we haven't set the time+ * yet, we need to set it from current time.+ */+static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec)+{+ time_t n;++ if (tm->tm_mday < 0)+ tm->tm_mday = now->tm_mday;+ if (tm->tm_mon < 0)+ tm->tm_mon = now->tm_mon;+ if (tm->tm_year < 0) {+ tm->tm_year = now->tm_year;+ if (tm->tm_mon > now->tm_mon)+ tm->tm_year--;+ }++ n = mktime(tm) - sec;+ p_localtime_r(&n, tm);+ return n;+}++static void date_now(struct tm *tm, struct tm *now, int *num)+{+ GIT_UNUSED(num);+ update_tm(tm, now, 0);+}++static void date_yesterday(struct tm *tm, struct tm *now, int *num)+{+ GIT_UNUSED(num);+ update_tm(tm, now, 24*60*60);+}++static void date_time(struct tm *tm, struct tm *now, int hour)+{+ if (tm->tm_hour < hour)+ date_yesterday(tm, now, NULL);+ tm->tm_hour = hour;+ tm->tm_min = 0;+ tm->tm_sec = 0;+}++static void date_midnight(struct tm *tm, struct tm *now, int *num)+{+ GIT_UNUSED(num);+ date_time(tm, now, 0);+}++static void date_noon(struct tm *tm, struct tm *now, int *num)+{+ GIT_UNUSED(num);+ date_time(tm, now, 12);+}++static void date_tea(struct tm *tm, struct tm *now, int *num)+{+ GIT_UNUSED(num);+ date_time(tm, now, 17);+}++static void date_pm(struct tm *tm, struct tm *now, int *num)+{+ int hour, n = *num;+ *num = 0;+ GIT_UNUSED(now);++ hour = tm->tm_hour;+ if (n) {+ hour = n;+ tm->tm_min = 0;+ tm->tm_sec = 0;+ }+ tm->tm_hour = (hour % 12) + 12;+}++static void date_am(struct tm *tm, struct tm *now, int *num)+{+ int hour, n = *num;+ *num = 0;+ GIT_UNUSED(now);++ hour = tm->tm_hour;+ if (n) {+ hour = n;+ tm->tm_min = 0;+ tm->tm_sec = 0;+ }+ tm->tm_hour = (hour % 12);+}++static void date_never(struct tm *tm, struct tm *now, int *num)+{+ time_t n = 0;+ GIT_UNUSED(now);+ GIT_UNUSED(num);+ p_localtime_r(&n, tm);+}++static const struct special {+ const char *name;+ void (*fn)(struct tm *, struct tm *, int *);+} special[] = {+ { "yesterday", date_yesterday },+ { "noon", date_noon },+ { "midnight", date_midnight },+ { "tea", date_tea },+ { "PM", date_pm },+ { "AM", date_am },+ { "never", date_never },+ { "now", date_now },+ { 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, struct tm *now, int *num, int *touched)+{+ const struct typelen *tl;+ const struct special *s;+ const char *end = date;+ int i;++ while (isalpha(*++end))+ /* scan to non-alpha */;++ for (i = 0; i < 12; i++) {+ size_t match = match_string(date, month_names[i]);+ if (match >= 3) {+ tm->tm_mon = i;+ *touched = 1;+ return end;+ }+ }++ for (s = special; s->name; s++) {+ size_t len = strlen(s->name);+ if (match_string(date, s->name) == len) {+ s->fn(tm, now, num);+ *touched = 1;+ return end;+ }+ }++ if (!*num) {+ for (i = 1; i < 11; i++) {+ size_t len = strlen(number_name[i]);+ if (match_string(date, number_name[i]) == len) {+ *num = i;+ *touched = 1;+ return end;+ }+ }+ if (match_string(date, "last") == 4) {+ *num = 1;+ *touched = 1;+ }+ return end;+ }++ tl = typelen;+ while (tl->type) {+ size_t len = strlen(tl->type);+ if (match_string(date, tl->type) >= len-1) {+ update_tm(tm, now, tl->length * *num);+ *num = 0;+ *touched = 1;+ return end;+ }+ tl++;+ }++ for (i = 0; i < 7; i++) {+ size_t 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, now, diff * 24 * 60 * 60);+ *touched = 1;+ return end;+ }+ }++ if (match_string(date, "months") >= 5) {+ int n;+ update_tm(tm, now, 0); /* fill in date fields if needed */+ n = tm->tm_mon - *num;+ *num = 0;+ while (n < 0) {+ n += 12;+ tm->tm_year--;+ }+ tm->tm_mon = n;+ *touched = 1;+ return end;+ }++ if (match_string(date, "years") >= 4) {+ update_tm(tm, now, 0); /* fill in date fields if needed */+ tm->tm_year -= *num;+ *num = 0;+ *touched = 1;+ return end;+ }++ return end;+}++static const char *approxidate_digit(const char *date, struct tm *tm, int *num)+{+ char *end;+ unsigned long number = strtoul(date, &end, 10);++ switch (*end) {+ case ':':+ case '.':+ case '/':+ case '-':+ if (isdigit(end[1])) {+ size_t match = match_multi_number(number, *end, date, end, tm);+ if (match)+ return date + match;+ }+ }++ /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */+ if (date[0] != '0' || end - date <= 2)+ *num = number;+ return end;+}++/*+ * Do we have a pending number at the end, or when+ * we see a new one? Let's assume it's a month day,+ * as in "Dec 6, 1992"+ */+static void pending_number(struct tm *tm, int *num)+{+ int number = *num;++ if (number) {+ *num = 0;+ if (tm->tm_mday < 0 && number < 32)+ tm->tm_mday = number;+ else if (tm->tm_mon < 0 && number < 13)+ tm->tm_mon = number-1;+ else if (tm->tm_year < 0) {+ if (number > 1969 && number < 2100)+ tm->tm_year = number - 1900;+ else if (number > 69 && number < 100)+ tm->tm_year = number;+ else if (number < 38)+ tm->tm_year = 100 + number;+ /* We mess up for number = 00 ? */+ }+ }+}++static git_time_t approxidate_str(const char *date,+ const struct timeval *tv,+ int *error_ret)+{+ int number = 0;+ int touched = 0;+ struct tm tm = {0}, now;+ time_t time_sec;++ time_sec = tv->tv_sec;+ p_localtime_r(&time_sec, &tm);+ now = tm;++ tm.tm_year = -1;+ tm.tm_mon = -1;+ tm.tm_mday = -1;++ for (;;) {+ unsigned char c = *date;+ if (!c)+ break;+ date++;+ if (isdigit(c)) {+ pending_number(&tm, &number);+ date = approxidate_digit(date-1, &tm, &number);+ touched = 1;+ continue;+ }+ if (isalpha(c))+ date = approxidate_alpha(date-1, &tm, &now, &number, &touched);+ }+ pending_number(&tm, &number);+ if (!touched)+ *error_ret = 1;+ return update_tm(&tm, &now, 0);+}++int git__date_parse(git_time_t *out, const char *date)+{+ struct timeval tv;+ git_time_t timestamp;+ int offset, error_ret=0;++ if (!parse_date_basic(date, ×tamp, &offset)) {+ *out = timestamp;+ return 0;+ }++ p_gettimeofday(&tv, NULL);+ *out = approxidate_str(date, &tv, &error_ret);+ return error_ret;+}
libgit2/src/delta-apply.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -33,6 +33,19 @@ } while (c & 0x80); *delta = d; *size = r;+ return 0;+}++int git__delta_read_header(+ const unsigned char *delta,+ size_t delta_len,+ size_t *base_sz,+ size_t *res_sz)+{+ const unsigned char *delta_end = delta + delta_len;+ if ((hdr_sz(base_sz, &delta, delta_end) < 0) ||+ (hdr_sz(res_sz, &delta, delta_end) < 0))+ return -1; return 0; }
libgit2/src/delta-apply.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -29,5 +29,22 @@ size_t base_len, const unsigned char *delta, size_t delta_len);++/**+ * Read the header of a git binary delta.+ *+ * @param delta the delta to execute copy/insert instructions from.+ * @param delta_len total number of bytes in the delta.+ * @param base_sz pointer to store the base size field.+ * @param res_sz pointer to store the result size field.+ * @return+ * - 0 on a successful decoding the header.+ * - GIT_ERROR if the delta is corrupt.+ */+extern int git__delta_read_header(+ const unsigned char *delta,+ size_t delta_len,+ size_t *base_sz,+ size_t *res_sz); #endif
+ libgit2/src/delta.c view
@@ -0,0 +1,424 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "delta.h"++/* maximum hash entry list for the same hash bucket */+#define HASH_LIMIT 64++#define RABIN_SHIFT 23+#define RABIN_WINDOW 16++static const unsigned int T[256] = {+ 0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,+ 0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,+ 0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85,+ 0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2,+ 0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a,+ 0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db,+ 0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753,+ 0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964,+ 0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8,+ 0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5,+ 0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81,+ 0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6,+ 0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e,+ 0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77,+ 0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff,+ 0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8,+ 0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc,+ 0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1,+ 0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d,+ 0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a,+ 0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2,+ 0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02,+ 0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a,+ 0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd,+ 0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61,+ 0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c,+ 0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08,+ 0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f,+ 0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7,+ 0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe,+ 0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76,+ 0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141,+ 0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65,+ 0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78,+ 0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4,+ 0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93,+ 0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b,+ 0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa,+ 0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872,+ 0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645,+ 0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99,+ 0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84,+ 0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811+};++static const unsigned int U[256] = {+ 0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a,+ 0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48,+ 0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511,+ 0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d,+ 0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8,+ 0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe,+ 0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb,+ 0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937,+ 0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e,+ 0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c,+ 0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d,+ 0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1,+ 0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4,+ 0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa,+ 0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef,+ 0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263,+ 0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302,+ 0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000,+ 0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59,+ 0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5,+ 0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90,+ 0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7,+ 0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2,+ 0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e,+ 0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467,+ 0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765,+ 0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604,+ 0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88,+ 0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd,+ 0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3,+ 0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996,+ 0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a,+ 0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b,+ 0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609,+ 0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50,+ 0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc,+ 0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99,+ 0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf,+ 0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa,+ 0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176,+ 0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f,+ 0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d,+ 0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a+};++struct index_entry {+ const unsigned char *ptr;+ unsigned int val;+ struct index_entry *next;+};++struct git_delta_index {+ unsigned long memsize;+ const void *src_buf;+ unsigned long src_size;+ unsigned int hash_mask;+ struct index_entry *hash[GIT_FLEX_ARRAY];+};++struct git_delta_index *+git_delta_create_index(const void *buf, unsigned long bufsize)+{+ unsigned int i, hsize, hmask, entries, prev_val, *hash_count;+ const unsigned char *data, *buffer = buf;+ struct git_delta_index *index;+ struct index_entry *entry, **hash;+ void *mem;+ unsigned long memsize;++ if (!buf || !bufsize)+ return NULL;++ /* Determine index hash size. Note that indexing skips the+ first byte to allow for optimizing the rabin polynomial+ initialization in create_delta(). */+ entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW;+ if (bufsize >= 0xffffffffUL) {+ /*+ * Current delta format can't encode offsets into+ * reference buffer with more than 32 bits.+ */+ entries = 0xfffffffeU / RABIN_WINDOW;+ }+ hsize = entries / 4;+ for (i = 4; (1u << i) < hsize && i < 31; i++);+ hsize = 1 << i;+ hmask = hsize - 1;++ /* allocate lookup index */+ memsize = sizeof(*index) ++ sizeof(*hash) * hsize ++ sizeof(*entry) * entries;+ mem = git__malloc(memsize);+ if (!mem)+ return NULL;+ index = mem;+ mem = index->hash;+ hash = mem;+ mem = hash + hsize;+ entry = mem;++ index->memsize = memsize;+ index->src_buf = buf;+ index->src_size = bufsize;+ index->hash_mask = hmask;+ memset(hash, 0, hsize * sizeof(*hash));++ /* allocate an array to count hash entries */+ hash_count = calloc(hsize, sizeof(*hash_count));+ if (!hash_count) {+ git__free(index);+ return NULL;+ }++ /* then populate the index */+ prev_val = ~0;+ for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW;+ data >= buffer;+ data -= RABIN_WINDOW) {+ unsigned int val = 0;+ for (i = 1; i <= RABIN_WINDOW; i++)+ val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];+ if (val == prev_val) {+ /* keep the lowest of consecutive identical blocks */+ entry[-1].ptr = data + RABIN_WINDOW;+ } else {+ prev_val = val;+ i = val & hmask;+ entry->ptr = data + RABIN_WINDOW;+ entry->val = val;+ entry->next = hash[i];+ hash[i] = entry++;+ hash_count[i]++;+ }+ }++ /*+ * Determine a limit on the number of entries in the same hash+ * bucket. This guard us against patological data sets causing+ * really bad hash distribution with most entries in the same hash+ * bucket that would bring us to O(m*n) computing costs (m and n+ * corresponding to reference and target buffer sizes).+ *+ * Make sure none of the hash buckets has more entries than+ * we're willing to test. Otherwise we cull the entry list+ * uniformly to still preserve a good repartition across+ * the reference buffer.+ */+ for (i = 0; i < hsize; i++) {+ if (hash_count[i] < HASH_LIMIT)+ continue;++ entry = hash[i];+ do {+ struct index_entry *keep = entry;+ int skip = hash_count[i] / HASH_LIMIT / 2;+ do {+ entry = entry->next;+ } while(--skip && entry);+ keep->next = entry;+ } while (entry);+ }+ git__free(hash_count);++ return index;+}++void git_delta_free_index(struct git_delta_index *index)+{+ git__free(index);+}++unsigned long git_delta_sizeof_index(struct git_delta_index *index)+{+ if (index)+ return index->memsize;+ else+ return 0;+}++/*+ * The maximum size for any opcode sequence, including the initial header+ * plus rabin window plus biggest copy.+ */+#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7)++void *+git_delta_create(+ const struct git_delta_index *index,+ const void *trg_buf,+ unsigned long trg_size,+ unsigned long *delta_size,+ unsigned long max_size)+{+ unsigned int i, outpos, outsize, moff, msize, val;+ int inscnt;+ const unsigned char *ref_data, *ref_top, *data, *top;+ unsigned char *out;++ if (!trg_buf || !trg_size)+ return NULL;++ outpos = 0;+ outsize = 8192;+ if (max_size && outsize >= max_size)+ outsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);+ out = git__malloc(outsize);+ if (!out)+ return NULL;++ /* store reference buffer size */+ i = index->src_size;+ while (i >= 0x80) {+ out[outpos++] = i | 0x80;+ i >>= 7;+ }+ out[outpos++] = i;++ /* store target buffer size */+ i = trg_size;+ while (i >= 0x80) {+ out[outpos++] = i | 0x80;+ i >>= 7;+ }+ out[outpos++] = i;++ ref_data = index->src_buf;+ ref_top = ref_data + index->src_size;+ data = trg_buf;+ top = (const unsigned char *) trg_buf + trg_size;++ outpos++;+ val = 0;+ for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {+ out[outpos++] = *data;+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];+ }+ inscnt = i;++ moff = 0;+ msize = 0;+ while (data < top) {+ if (msize < 4096) {+ struct index_entry *entry;+ val ^= U[data[-RABIN_WINDOW]];+ val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];+ i = val & index->hash_mask;+ for (entry = index->hash[i]; entry; entry = entry->next) {+ const unsigned char *ref = entry->ptr;+ const unsigned char *src = data;+ unsigned int ref_size = (unsigned int)(ref_top - ref);+ if (entry->val != val)+ continue;+ if (ref_size > (unsigned int)(top - src))+ ref_size = (unsigned int)(top - src);+ if (ref_size <= msize)+ break;+ while (ref_size-- && *src++ == *ref)+ ref++;+ if (msize < (unsigned int)(ref - entry->ptr)) {+ /* this is our best match so far */+ msize = (unsigned int)(ref - entry->ptr);+ moff = (unsigned int)(entry->ptr - ref_data);+ if (msize >= 4096) /* good enough */+ break;+ }+ }+ }++ if (msize < 4) {+ if (!inscnt)+ outpos++;+ out[outpos++] = *data++;+ inscnt++;+ if (inscnt == 0x7f) {+ out[outpos - inscnt - 1] = inscnt;+ inscnt = 0;+ }+ msize = 0;+ } else {+ unsigned int left;+ unsigned char *op;++ if (inscnt) {+ while (moff && ref_data[moff-1] == data[-1]) {+ /* we can match one byte back */+ msize++;+ moff--;+ data--;+ outpos--;+ if (--inscnt)+ continue;+ outpos--; /* remove count slot */+ inscnt--; /* make it -1 */+ break;+ }+ out[outpos - inscnt - 1] = inscnt;+ inscnt = 0;+ }++ /* A copy op is currently limited to 64KB (pack v2) */+ left = (msize < 0x10000) ? 0 : (msize - 0x10000);+ msize -= left;++ op = out + outpos++;+ i = 0x80;++ if (moff & 0x000000ff)+ out[outpos++] = moff >> 0, i |= 0x01;+ if (moff & 0x0000ff00)+ out[outpos++] = moff >> 8, i |= 0x02;+ if (moff & 0x00ff0000)+ out[outpos++] = moff >> 16, i |= 0x04;+ if (moff & 0xff000000)+ out[outpos++] = moff >> 24, i |= 0x08;++ if (msize & 0x00ff)+ out[outpos++] = msize >> 0, i |= 0x10;+ if (msize & 0xff00)+ out[outpos++] = msize >> 8, i |= 0x20;++ *op = i;++ data += msize;+ moff += msize;+ msize = left;++ if (msize < 4096) {+ int j;+ val = 0;+ for (j = -RABIN_WINDOW; j < 0; j++)+ val = ((val << 8) | data[j])+ ^ T[val >> RABIN_SHIFT];+ }+ }++ if (outpos >= outsize - MAX_OP_SIZE) {+ void *tmp = out;+ outsize = outsize * 3 / 2;+ if (max_size && outsize >= max_size)+ outsize = max_size + MAX_OP_SIZE + 1;+ if (max_size && outpos > max_size)+ break;+ out = git__realloc(out, outsize);+ if (!out) {+ git__free(tmp);+ return NULL;+ }+ }+ }++ if (inscnt)+ out[outpos - inscnt - 1] = inscnt;++ if (max_size && outpos > max_size) {+ git__free(out);+ return NULL;+ }++ *delta_size = outpos;+ return out;+}
+ libgit2/src/delta.h view
@@ -0,0 +1,114 @@+/*+ * diff-delta code taken from git.git. See diff-delta.c for details.+ *+ */+#ifndef INCLUDE_git_delta_h__+#define INCLUDE_git_delta_h__++#include "common.h"++/* opaque object for delta index */+struct git_delta_index;++/*+ * create_delta_index: compute index data from given buffer+ *+ * This returns a pointer to a struct delta_index that should be passed to+ * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer+ * is returned on failure. The given buffer must not be freed nor altered+ * before free_delta_index() is called. The returned pointer must be freed+ * using free_delta_index().+ */+extern struct git_delta_index *+git_delta_create_index(const void *buf, unsigned long bufsize);++/*+ * free_delta_index: free the index created by create_delta_index()+ *+ * Given pointer must be what create_delta_index() returned, or NULL.+ */+extern void git_delta_free_index(struct git_delta_index *index);++/*+ * sizeof_delta_index: returns memory usage of delta index+ *+ * Given pointer must be what create_delta_index() returned, or NULL.+ */+extern unsigned long git_delta_sizeof_index(struct git_delta_index *index);++/*+ * create_delta: create a delta from given index for the given buffer+ *+ * This function may be called multiple times with different buffers using+ * the same delta_index pointer. If max_delta_size is non-zero and the+ * resulting delta is to be larger than max_delta_size then NULL is returned.+ * On success, a non-NULL pointer to the buffer with the delta data is+ * returned and *delta_size is updated with its size. The returned buffer+ * must be freed by the caller.+ */+extern void *git_delta_create(+ const struct git_delta_index *index,+ const void *buf,+ unsigned long bufsize,+ unsigned long *delta_size,+ unsigned long max_delta_size);++/*+ * diff_delta: create a delta from source buffer to target buffer+ *+ * If max_delta_size is non-zero and the resulting delta is to be larger+ * than max_delta_size then NULL is returned. On success, a non-NULL+ * pointer to the buffer with the delta data is returned and *delta_size is+ * updated with its size. The returned buffer must be freed by the caller.+ */+GIT_INLINE(void *) git_delta(+ const void *src_buf, unsigned long src_bufsize,+ const void *trg_buf, unsigned long trg_bufsize,+ unsigned long *delta_size,+ unsigned long max_delta_size)+{+ struct git_delta_index *index = git_delta_create_index(src_buf, src_bufsize);+ if (index) {+ void *delta = git_delta_create(+ index, trg_buf, trg_bufsize, delta_size, max_delta_size);+ git_delta_free_index(index);+ return delta;+ }+ return NULL;+}++/*+ * patch_delta: recreate target buffer given source buffer and delta data+ *+ * On success, a non-NULL pointer to the target buffer is returned and+ * *trg_bufsize is updated with its size. On failure a NULL pointer is+ * returned. The returned buffer must be freed by the caller.+ */+extern void *git_delta_patch(+ const void *src_buf, unsigned long src_size,+ const void *delta_buf, unsigned long delta_size,+ unsigned long *dst_size);++/* the smallest possible delta size is 4 bytes */+#define GIT_DELTA_SIZE_MIN 4++/*+ * This must be called twice on the delta data buffer, first to get the+ * expected source buffer size, and again to get the target buffer size.+ */+GIT_INLINE(unsigned long) git_delta_get_hdr_size(+ const unsigned char **datap, const unsigned char *top)+{+ const unsigned char *data = *datap;+ unsigned long cmd, size = 0;+ int i = 0;+ do {+ cmd = *data++;+ size |= (cmd & 0x7f) << i;+ i += 7;+ } while (cmd & 0x80 && data < top);+ *datap = data;+ return size;+}++#endif
libgit2/src/diff.c view
@@ -1,74 +1,19 @@ /*- * Copyright (C) 2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h"-#include "git2/diff.h" #include "diff.h" #include "fileops.h" #include "config.h" #include "attr_file.h"--static char *diff_prefix_from_pathspec(const git_strarray *pathspec)-{- git_buf prefix = GIT_BUF_INIT;- const char *scan;-- if (git_buf_common_prefix(&prefix, pathspec) < 0)- return NULL;-- /* diff prefix will only be leading non-wildcards */- for (scan = prefix.ptr; *scan && !git__iswildcard(*scan); ++scan);- git_buf_truncate(&prefix, scan - prefix.ptr);-- if (prefix.size > 0)- return git_buf_detach(&prefix);-- git_buf_free(&prefix);- return NULL;-}--static bool diff_pathspec_is_interesting(const git_strarray *pathspec)-{- const char *str;-- if (pathspec == NULL || pathspec->count == 0)- return false;- if (pathspec->count > 1)- return true;-- str = pathspec->strings[0];- if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))- return false;- return true;-}--static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path)-{- unsigned int i;- git_attr_fnmatch *match;-- if (!diff->pathspec.length)- return true;-- git_vector_foreach(&diff->pathspec, i, match) {- int result = p_fnmatch(match->pattern, path, 0);-- /* if we didn't match, look for exact dirname prefix match */- if (result == FNM_NOMATCH &&- (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&- strncmp(path, match->pattern, match->length) == 0 &&- path[match->length] == '/')- result = 0;-- if (result == 0)- return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;- }+#include "filter.h"+#include "pathspec.h" - return false;-}+#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)+#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0) static git_diff_delta *diff_delta__alloc( git_diff_list *diff,@@ -87,7 +32,7 @@ delta->new_file.path = delta->old_file.path; - if (diff->opts.flags & GIT_DIFF_REVERSE) {+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { switch (status) { case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;@@ -99,70 +44,16 @@ return delta; } -static git_diff_delta *diff_delta__dup(- const git_diff_delta *d, git_pool *pool)-{- git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));- if (!delta)- return NULL;-- memcpy(delta, d, sizeof(git_diff_delta));-- delta->old_file.path = git_pool_strdup(pool, d->old_file.path);- if (delta->old_file.path == NULL)- goto fail;-- if (d->new_file.path != d->old_file.path) {- delta->new_file.path = git_pool_strdup(pool, d->new_file.path);- if (delta->new_file.path == NULL)- goto fail;- } else {- delta->new_file.path = delta->old_file.path;- }-- return delta;--fail:- git__free(delta);- return NULL;-}--static git_diff_delta *diff_delta__merge_like_cgit(- const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)+static int diff_notify(+ const git_diff_list *diff,+ const git_diff_delta *delta,+ const char *matched_pathspec) {- git_diff_delta *dup = diff_delta__dup(a, pool);- if (!dup)- return NULL;-- if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0)- return dup;-- git_oid_cpy(&dup->new_file.oid, &b->new_file.oid);-- dup->new_file.mode = b->new_file.mode;- dup->new_file.size = b->new_file.size;- dup->new_file.flags = b->new_file.flags;-- /* Emulate C git for merging two diffs (a la 'git diff <sha>').- *- * When C git does a diff between the work dir and a tree, it actually- * diffs with the index but uses the workdir contents. This emulates- * those choices so we can emulate the type of diff.- */- if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) {- if (dup->status == GIT_DELTA_DELETED)- /* preserve pending delete info */;- else if (b->status == GIT_DELTA_UNTRACKED ||- b->status == GIT_DELTA_IGNORED)- dup->status = b->status;- else- dup->status = GIT_DELTA_UNMODIFIED;- }- else if (dup->status == GIT_DELTA_UNMODIFIED ||- b->status == GIT_DELTA_DELETED)- dup->status = b->status;+ if (!diff->opts.notify_cb)+ return 0; - return dup;+ return diff->opts.notify_cb(+ diff, delta, matched_pathspec, diff->opts.notify_payload); } static int diff_delta__from_one(@@ -171,18 +62,28 @@ const git_index_entry *entry) { git_diff_delta *delta;+ const char *matched_pathspec;+ int notify_res; if (status == GIT_DELTA_IGNORED &&- (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) return 0; if (status == GIT_DELTA_UNTRACKED &&- (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) return 0; - if (!diff_path_matches_pathspec(diff, entry->path))+ if (entry->mode == GIT_FILEMODE_COMMIT &&+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) return 0; + if (!git_pathspec_match_path(+ &diff->pathspec, entry->path,+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),+ &matched_pathspec))+ return 0;+ delta = diff_delta__alloc(diff, status, entry->path); GITERR_CHECK_ALLOC(delta); @@ -199,56 +100,124 @@ git_oid_cpy(&delta->new_file.oid, &entry->oid); } - delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;- delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; - if (git_vector_insert(&diff->deltas, delta) < 0) {+ if (delta->status == GIT_DELTA_DELETED ||+ !git_oid_iszero(&delta->new_file.oid))+ delta->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;++ notify_res = diff_notify(diff, delta, matched_pathspec);++ if (notify_res) git__free(delta);+ else if (git_vector_insert(&diff->deltas, delta) < 0) {+ git__free(delta); return -1; } - return 0;+ return notify_res < 0 ? GIT_EUSER : 0; } static int diff_delta__from_two( git_diff_list *diff, git_delta_t status, const git_index_entry *old_entry,+ uint32_t old_mode, const git_index_entry *new_entry,- git_oid *new_oid)+ uint32_t new_mode,+ git_oid *new_oid,+ const char *matched_pathspec) { git_diff_delta *delta;+ int notify_res; if (status == GIT_DELTA_UNMODIFIED &&- (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) return 0; - if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {- const git_index_entry *temp = old_entry;+ if (old_entry->mode == GIT_FILEMODE_COMMIT &&+ new_entry->mode == GIT_FILEMODE_COMMIT &&+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))+ return 0;++ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {+ uint32_t temp_mode = old_mode;+ const git_index_entry *temp_entry = old_entry; old_entry = new_entry;- new_entry = temp;+ new_entry = temp_entry;+ old_mode = new_mode;+ new_mode = temp_mode; } delta = diff_delta__alloc(diff, status, old_entry->path); GITERR_CHECK_ALLOC(delta); - delta->old_file.mode = old_entry->mode; git_oid_cpy(&delta->old_file.oid, &old_entry->oid);- delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;+ delta->old_file.size = old_entry->file_size;+ delta->old_file.mode = old_mode;+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; - delta->new_file.mode = new_entry->mode;- git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid);+ git_oid_cpy(&delta->new_file.oid, &new_entry->oid);+ delta->new_file.size = new_entry->file_size;+ delta->new_file.mode = new_mode;++ if (new_oid) {+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))+ git_oid_cpy(&delta->old_file.oid, new_oid);+ else+ git_oid_cpy(&delta->new_file.oid, new_oid);+ }+ if (new_oid || !git_oid_iszero(&new_entry->oid))- delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;+ delta->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; - if (git_vector_insert(&diff->deltas, delta) < 0) {+ notify_res = diff_notify(diff, delta, matched_pathspec);++ if (notify_res) git__free(delta);+ else if (git_vector_insert(&diff->deltas, delta) < 0) {+ git__free(delta); return -1; } - return 0;+ return notify_res < 0 ? GIT_EUSER : 0; } +static git_diff_delta *diff_delta__last_for_item(+ git_diff_list *diff,+ const git_index_entry *item)+{+ git_diff_delta *delta = git_vector_last(&diff->deltas);+ if (!delta)+ return NULL;++ switch (delta->status) {+ case GIT_DELTA_UNMODIFIED:+ case GIT_DELTA_DELETED:+ if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0)+ return delta;+ break;+ case GIT_DELTA_ADDED:+ if (git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)+ return delta;+ break;+ case GIT_DELTA_UNTRACKED:+ if (diff->strcomp(delta->new_file.path, item->path) == 0 &&+ git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)+ return delta;+ break;+ case GIT_DELTA_MODIFIED:+ if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0 ||+ git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)+ return delta;+ break;+ default:+ break;+ }++ return NULL;+}+ static char *diff_strdup_prefix(git_pool *pool, const char *prefix) { size_t len = strlen(prefix);@@ -260,13 +229,34 @@ return git_pool_strndup(pool, prefix, len + 1); } -static int diff_delta__cmp(const void *a, const void *b)+int git_diff_delta__cmp(const void *a, const void *b) { const git_diff_delta *da = a, *db = b; int val = strcmp(da->old_file.path, db->old_file.path); return val ? val : ((int)da->status - (int)db->status); } +bool git_diff_delta__should_skip(+ const git_diff_options *opts, const git_diff_delta *delta)+{+ uint32_t flags = opts ? opts->flags : 0;++ if (delta->status == GIT_DELTA_UNMODIFIED &&+ (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)+ return true;++ if (delta->status == GIT_DELTA_IGNORED &&+ (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)+ return true;++ if (delta->status == GIT_DELTA_UNTRACKED &&+ (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)+ return true;++ return false;+}++ static int config_bool(git_config *cfg, const char *name, int defvalue) { int val = defvalue;@@ -281,14 +271,14 @@ git_repository *repo, const git_diff_options *opts) { git_config *cfg;- size_t i; git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); if (diff == NULL) return NULL; + GIT_REFCOUNT_INC(diff); diff->repo = repo; - if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 ||+ if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 || git_pool_init(&diff->pool, 1, 0) < 0) goto fail; @@ -300,17 +290,37 @@ if (config_bool(cfg, "core.ignorestat", 0)) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED; if (config_bool(cfg, "core.filemode", 1))- diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT;+ diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS; if (config_bool(cfg, "core.trustctime", 1)) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ - if (opts == NULL)+ /* TODO: there are certain config settings where even if we were+ * not given an options structure, we need the diff list to have one+ * so that we can store the altered default values.+ *+ * - diff.ignoreSubmodules+ * - diff.mnemonicprefix+ * - diff.noprefix+ */++ if (opts == NULL) {+ /* Make sure we default to 3 lines */+ diff->opts.context_lines = 3; return diff;+ } memcpy(&diff->opts, opts, sizeof(git_diff_options));- memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec)); + if(opts->flags & GIT_DIFF_IGNORE_FILEMODE)+ diff->diffcaps = diff->diffcaps & ~GIT_DIFFCAPS_TRUST_MODE_BITS;++ /* pathspec init will do nothing for empty pathspec */+ if (git_pathspec_init(&diff->pathspec, &opts->pathspec, &diff->pool) < 0)+ goto fail;++ /* TODO: handle config diff.mnemonicprefix, diff.noprefix */+ diff->opts.old_prefix = diff_strdup_prefix(&diff->pool, opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT); diff->opts.new_prefix = diff_strdup_prefix(&diff->pool,@@ -319,39 +329,15 @@ if (!diff->opts.old_prefix || !diff->opts.new_prefix) goto fail; - if (diff->opts.flags & GIT_DIFF_REVERSE) {- char *swap = diff->opts.old_prefix;+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {+ const char *swap = diff->opts.old_prefix; diff->opts.old_prefix = diff->opts.new_prefix; diff->opts.new_prefix = swap; } - /* only copy pathspec if it is "interesting" so we can test- * diff->pathspec.length > 0 to know if it is worth calling- * fnmatch as we iterate.- */- if (!diff_pathspec_is_interesting(&opts->pathspec))- return diff;-- if (git_vector_init(- &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0)- goto fail;-- for (i = 0; i < opts->pathspec.count; ++i) {- int ret;- const char *pattern = opts->pathspec.strings[i];- git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));- if (!match)- goto fail;- ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern);- if (ret == GIT_ENOTFOUND) {- git__free(match);- continue;- } else if (ret < 0)- goto fail;-- if (git_vector_insert(&diff->pathspec, match) < 0)- goto fail;- }+ /* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))+ diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; return diff; @@ -360,65 +346,106 @@ return NULL; } -void git_diff_list_free(git_diff_list *diff)+static void diff_list_free(git_diff_list *diff) { git_diff_delta *delta;- git_attr_fnmatch *match; unsigned int i; - if (!diff)- return;- git_vector_foreach(&diff->deltas, i, delta) { git__free(delta); diff->deltas.contents[i] = NULL; } git_vector_free(&diff->deltas); - git_vector_foreach(&diff->pathspec, i, match) {- git__free(match);- diff->pathspec.contents[i] = NULL;- }- git_vector_free(&diff->pathspec);-+ git_pathspec_free(&diff->pathspec); git_pool_clear(&diff->pool); git__free(diff); } -static int oid_for_workdir_item(+void git_diff_list_free(git_diff_list *diff)+{+ if (!diff)+ return;++ GIT_REFCOUNT_DEC(diff, diff_list_free);+}++void git_diff_list_addref(git_diff_list *diff)+{+ GIT_REFCOUNT_INC(diff);+}++int git_diff__oid_for_file( git_repository *repo,- const git_index_entry *item,+ const char *path,+ uint16_t mode,+ git_off_t size, git_oid *oid) {- int result;+ int result = 0; git_buf full_path = GIT_BUF_INIT; - if (git_buf_joinpath(&full_path, git_repository_workdir(repo), item->path) < 0)+ if (git_buf_joinpath(+ &full_path, git_repository_workdir(repo), path) < 0) return -1; - /* calculate OID for file if possible*/- if (S_ISLNK(item->mode))+ if (!mode) {+ struct stat st;++ if (p_stat(path, &st) < 0) {+ giterr_set(GITERR_OS, "Could not stat '%s'", path);+ result = -1;+ goto cleanup;+ }++ mode = st.st_mode;+ size = st.st_size;+ }++ /* calculate OID for file if possible */+ if (S_ISGITLINK(mode)) {+ git_submodule *sm;+ const git_oid *sm_oid;++ if (!git_submodule_lookup(&sm, repo, path) &&+ (sm_oid = git_submodule_wd_id(sm)) != NULL)+ git_oid_cpy(oid, sm_oid);+ else {+ /* if submodule lookup failed probably just in an intermediate+ * state where some init hasn't happened, so ignore the error+ */+ giterr_clear();+ memset(oid, 0, sizeof(*oid));+ }+ } else if (S_ISLNK(mode)) { result = git_odb__hashlink(oid, full_path.ptr);- else if (!git__is_sizet(item->file_size)) {- giterr_set(GITERR_OS, "File size overflow for 32-bit systems");+ } else if (!git__is_sizet(size)) {+ giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path); result = -1; } else {- int fd = git_futils_open_ro(full_path.ptr);- if (fd < 0)- result = fd;- else {- result = git_odb__hashfd(- oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB);- p_close(fd);+ git_vector filters = GIT_VECTOR_INIT;++ result = git_filters_load(&filters, repo, path, GIT_FILTER_TO_ODB);+ if (result >= 0) {+ int fd = git_futils_open_ro(full_path.ptr);+ if (fd < 0)+ result = fd;+ else {+ result = git_odb__hashfd_filtered(+ oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters);+ p_close(fd);+ } }++ git_filters_free(&filters); } +cleanup: git_buf_free(&full_path);- return result; } -#define EXEC_BIT_MASK 0000111+#define MODE_BITS_MASK 0000777 static int maybe_modified( git_iterator *old_iter,@@ -431,24 +458,30 @@ git_delta_t status = GIT_DELTA_MODIFIED; unsigned int omode = oitem->mode; unsigned int nmode = nitem->mode;+ bool new_is_workdir = (new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);+ const char *matched_pathspec; GIT_UNUSED(old_iter); - if (!diff_path_matches_pathspec(diff, oitem->path))+ if (!git_pathspec_match_path(+ &diff->pathspec, oitem->path,+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH),+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE),+ &matched_pathspec)) return 0; - /* on platforms with no symlinks, promote plain files to symlinks */- if (S_ISLNK(omode) && S_ISREG(nmode) &&+ /* on platforms with no symlinks, preserve mode of existing symlinks */+ if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))- nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);+ nmode = omode; - /* on platforms with no execmode, clear exec bit from comparisons */- if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) {- omode = omode & ~EXEC_BIT_MASK;- nmode = nmode & ~EXEC_BIT_MASK;- }+ /* on platforms with no execmode, just preserve old mode */+ if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&+ (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&+ new_is_workdir)+ nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); - /* support "assume unchanged" (badly, b/c we still stat everything) */+ /* support "assume unchanged" (poorly, b/c we still stat everything) */ if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0) status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ? GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;@@ -459,23 +492,29 @@ /* if basic type of file changed, then split into delete and add */ else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {- if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||- diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)- return -1;- return 0;+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE))+ status = GIT_DELTA_TYPECHANGE;+ else {+ if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||+ diff_delta__from_one(diff, GIT_DELTA_ADDED, nitem) < 0)+ return -1;+ return 0;+ } } /* if oids and modes match, then file is unmodified */- else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&- omode == nmode)+ else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode) status = GIT_DELTA_UNMODIFIED; - /* if we have a workdir item with an unknown oid, check deeper */- else if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) {+ /* if we have an unknown OID and a workdir iterator, then check some+ * circumstances that can accelerate things or need special handling+ */+ else if (git_oid_iszero(&nitem->oid) && new_is_workdir) { /* TODO: add check against index file st_mtime to avoid racy-git */ - /* if they files look exactly alike, then we'll assume the same */- if (oitem->file_size == nitem->file_size &&+ /* if the stat data looks exactly alike, then assume the same */+ if (omode == nmode &&+ oitem->file_size == nitem->file_size && (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) || (oitem->ctime.seconds == nitem->ctime.seconds)) && oitem->mtime.seconds == nitem->mtime.seconds &&@@ -487,77 +526,193 @@ status = GIT_DELTA_UNMODIFIED; else if (S_ISGITLINK(nmode)) {+ int err; git_submodule *sub; - if ((diff->opts.flags & GIT_DIFF_IGNORE_SUBMODULES) != 0)+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) status = GIT_DELTA_UNMODIFIED;- else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0)- return -1;- else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL)+ else if ((err = git_submodule_lookup(&sub, diff->repo, nitem->path)) < 0) {+ if (err == GIT_EEXISTS)+ status = GIT_DELTA_UNMODIFIED;+ else+ return err;+ } else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) status = GIT_DELTA_UNMODIFIED; else {- /* TODO: support other GIT_SUBMODULE_IGNORE values */- status = GIT_DELTA_UNMODIFIED;+ unsigned int sm_status = 0;+ if (git_submodule_status(&sm_status, sub) < 0)+ return -1;++ /* check IS_WD_UNMODIFIED because this case is only used+ * when the new side of the diff is the working directory+ */+ status = GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)+ ? GIT_DELTA_UNMODIFIED : GIT_DELTA_MODIFIED;++ /* grab OID while we are here */+ if (git_oid_iszero(&nitem->oid)) {+ const git_oid *sm_oid = git_submodule_wd_id(sub);+ if (sm_oid != NULL) {+ git_oid_cpy(&noid, sm_oid);+ use_noid = &noid;+ }+ } } }+ } - /* TODO: check git attributes so we will not have to read the file- * in if it is marked binary.- */+ /* if mode is GITLINK and submodules are ignored, then skip */+ else if (S_ISGITLINK(nmode) &&+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES))+ status = GIT_DELTA_UNMODIFIED; - else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)- return -1;+ /* if we got here and decided that the files are modified, but we+ * haven't calculated the OID of the new item, then calculate it now+ */+ if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) {+ if (!use_noid) {+ if (git_diff__oid_for_file(diff->repo,+ nitem->path, nitem->mode, nitem->file_size, &noid) < 0)+ return -1;+ use_noid = &noid;+ } - else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&- omode == nmode)+ /* if oid matches, then mark unmodified (except submodules, where+ * the filesystem content may be modified even if the oid still+ * matches between the index and the workdir HEAD)+ */+ if (omode == nmode && !S_ISGITLINK(omode) &&+ git_oid_equal(&oitem->oid, use_noid)) status = GIT_DELTA_UNMODIFIED;+ } - /* store calculated oid so we don't have to recalc later */- use_noid = &noid;+ return diff_delta__from_two(+ diff, status, oitem, omode, nitem, nmode, use_noid, matched_pathspec);+}++static bool entry_is_prefixed(+ git_diff_list *diff,+ const git_index_entry *item,+ const git_index_entry *prefix_item)+{+ size_t pathlen;++ if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0)+ return false;++ pathlen = strlen(prefix_item->path);++ return (prefix_item->path[pathlen - 1] == '/' ||+ item->path[pathlen] == '\0' ||+ item->path[pathlen] == '/');+}++static int diff_list_init_from_iterators(+ git_diff_list *diff,+ git_iterator *old_iter,+ git_iterator *new_iter)+{+ diff->old_src = old_iter->type;+ diff->new_src = new_iter->type;++ /* Use case-insensitive compare if either iterator has+ * the ignore_case bit set */+ if (!git_iterator_ignore_case(old_iter) &&+ !git_iterator_ignore_case(new_iter))+ {+ diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE;++ diff->strcomp = git__strcmp;+ diff->strncomp = git__strncmp;+ diff->pfxcomp = git__prefixcmp;+ diff->entrycomp = git_index_entry__cmp;+ } else {+ diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;++ diff->strcomp = git__strcasecmp;+ diff->strncomp = git__strncasecmp;+ diff->pfxcomp = git__prefixcmp_icase;+ diff->entrycomp = git_index_entry__cmp_icase; } - return diff_delta__from_two(diff, status, oitem, nitem, use_noid);+ return 0; } -static int diff_from_iterators(+int git_diff__from_iterators(+ git_diff_list **diff_ptr, git_repository *repo,- const git_diff_options *opts, /**< can be NULL for defaults */ git_iterator *old_iter, git_iterator *new_iter,- git_diff_list **diff_ptr)+ const git_diff_options *opts) {+ int error = 0; const git_index_entry *oitem, *nitem; git_buf ignore_prefix = GIT_BUF_INIT; git_diff_list *diff = git_diff_list_alloc(repo, opts);- if (!diff)++ *diff_ptr = NULL;++ if (!diff || diff_list_init_from_iterators(diff, old_iter, new_iter) < 0) goto fail; - diff->old_src = old_iter->type;- diff->new_src = new_iter->type;+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE)) {+ if (git_iterator_set_ignore_case(old_iter, true) < 0 ||+ git_iterator_set_ignore_case(new_iter, true) < 0)+ goto fail;+ } - if (git_iterator_current(old_iter, &oitem) < 0 ||- git_iterator_current(new_iter, &nitem) < 0)+ if (git_iterator_current(&oitem, old_iter) < 0 ||+ git_iterator_current(&nitem, new_iter) < 0) goto fail; /* run iterators building diffs */ while (oitem || nitem) {+ int cmp = oitem ? (nitem ? diff->entrycomp(oitem, nitem) : -1) : 1; /* create DELETED records for old items not matched in new */- if (oitem && (!nitem || strcmp(oitem->path, nitem->path) < 0)) {- if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||- git_iterator_advance(old_iter, &oitem) < 0)+ if (cmp < 0) {+ if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0) goto fail;++ /* if we are generating TYPECHANGE records then check for that+ * instead of just generating a DELETE record+ */+ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&+ entry_is_prefixed(diff, nitem, oitem))+ {+ /* this entry has become a tree! convert to TYPECHANGE */+ git_diff_delta *last = diff_delta__last_for_item(diff, oitem);+ if (last) {+ last->status = GIT_DELTA_TYPECHANGE;+ last->new_file.mode = GIT_FILEMODE_TREE;+ }++ /* If new_iter is a workdir iterator, then this situation+ * will certainly be followed by a series of untracked items.+ * Unless RECURSE_UNTRACKED_DIRS is set, skip over them...+ */+ if (S_ISDIR(nitem->mode) &&+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))+ {+ if (git_iterator_advance(&nitem, new_iter) < 0)+ goto fail;+ }+ }++ if (git_iterator_advance(&oitem, old_iter) < 0)+ goto fail; } /* create ADDED, TRACKED, or IGNORED records for new items not * matched in old (and/or descend into directories as needed) */- else if (nitem && (!oitem || strcmp(oitem->path, nitem->path) > 0)) {+ else if (cmp > 0) { git_delta_t delta_type = GIT_DELTA_UNTRACKED;+ bool contains_oitem = entry_is_prefixed(diff, oitem, nitem); /* check if contained in ignored parent directory */ if (git_buf_len(&ignore_prefix) &&- git__prefixcmp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0)+ diff->pfxcomp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0) delta_type = GIT_DELTA_IGNORED; if (S_ISDIR(nitem->mode)) {@@ -565,20 +720,48 @@ * it or if the user requested the contents of untracked * directories and it is not under an ignored directory. */- if ((oitem && git__prefixcmp(oitem->path, nitem->path) == 0) ||+ bool recurse_into_dir = (delta_type == GIT_DELTA_UNTRACKED &&- (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0))- {- /* if this directory is ignored, remember it as the- * "ignore_prefix" for processing contained items- */- if (delta_type == GIT_DELTA_UNTRACKED &&- git_iterator_current_is_ignored(new_iter))- git_buf_sets(&ignore_prefix, nitem->path);+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||+ (delta_type == GIT_DELTA_IGNORED &&+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); - if (git_iterator_advance_into_directory(new_iter, &nitem) < 0)+ /* do not advance into directories that contain a .git file */+ if (!contains_oitem && recurse_into_dir) {+ git_buf *full = NULL;+ if (git_iterator_current_workdir_path(&full, new_iter) < 0) goto fail;+ if (git_path_contains_dir(full, DOT_GIT))+ recurse_into_dir = false;+ } + /* if directory is ignored, remember ignore_prefix */+ if ((contains_oitem || recurse_into_dir) &&+ delta_type == GIT_DELTA_UNTRACKED &&+ git_iterator_current_is_ignored(new_iter))+ {+ git_buf_sets(&ignore_prefix, nitem->path);+ delta_type = GIT_DELTA_IGNORED;++ /* skip recursion if we've just learned this is ignored */+ if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS))+ recurse_into_dir = false;+ }++ if (contains_oitem || recurse_into_dir) {+ /* advance into directory */+ error = git_iterator_advance_into(&nitem, new_iter);++ /* if directory is empty, can't advance into it, so skip */+ if (error == GIT_ENOTFOUND) {+ giterr_clear();+ error = git_iterator_advance(&nitem, new_iter);++ git_buf_clear(&ignore_prefix);+ }++ if (error < 0)+ goto fail; continue; } }@@ -598,8 +781,9 @@ * checked before container directory exclusions are used to * skip the file. */- else if (delta_type == GIT_DELTA_IGNORED) {- if (git_iterator_advance(new_iter, &nitem) < 0)+ else if (delta_type == GIT_DELTA_IGNORED &&+ DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) {+ if (git_iterator_advance(&nitem, new_iter) < 0) goto fail; continue; /* ignored parent directory, so skip completely */ }@@ -607,177 +791,145 @@ else if (git_iterator_current_is_ignored(new_iter)) delta_type = GIT_DELTA_IGNORED; - else if (new_iter->type != GIT_ITERATOR_WORKDIR)+ else if (new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) delta_type = GIT_DELTA_ADDED; - if (diff_delta__from_one(diff, delta_type, nitem) < 0 ||- git_iterator_advance(new_iter, &nitem) < 0)+ if (diff_delta__from_one(diff, delta_type, nitem) < 0) goto fail;++ /* if we are generating TYPECHANGE records then check for that+ * instead of just generating an ADDED/UNTRACKED record+ */+ if (delta_type != GIT_DELTA_IGNORED &&+ DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&+ contains_oitem)+ {+ /* this entry was prefixed with a tree - make TYPECHANGE */+ git_diff_delta *last = diff_delta__last_for_item(diff, nitem);+ if (last) {+ last->status = GIT_DELTA_TYPECHANGE;+ last->old_file.mode = GIT_FILEMODE_TREE;+ }+ }++ if (git_iterator_advance(&nitem, new_iter) < 0)+ goto fail; } /* otherwise item paths match, so create MODIFIED record * (or ADDED and DELETED pair if type changed) */ else {- assert(oitem && nitem && strcmp(oitem->path, nitem->path) == 0);+ assert(oitem && nitem && cmp == 0); if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||- git_iterator_advance(old_iter, &oitem) < 0 ||- git_iterator_advance(new_iter, &nitem) < 0)+ git_iterator_advance(&oitem, old_iter) < 0 ||+ git_iterator_advance(&nitem, new_iter) < 0) goto fail; } } - git_iterator_free(old_iter);- git_iterator_free(new_iter);- git_buf_free(&ignore_prefix);- *diff_ptr = diff;- return 0; fail:- git_iterator_free(old_iter);- git_iterator_free(new_iter);+ if (!*diff_ptr) {+ git_diff_list_free(diff);+ error = -1;+ }+ git_buf_free(&ignore_prefix); - git_diff_list_free(diff);- *diff_ptr = NULL;- return -1;+ return error; } +#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \+ git_iterator *a = NULL, *b = NULL; \+ char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \+ if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \+ error = git_diff__from_iterators(diff, repo, a, b, opts); \+ git__free(pfx); git_iterator_free(a); git_iterator_free(b); \+} while (0) int git_diff_tree_to_tree(+ git_diff_list **diff, git_repository *repo,- const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old_tree, git_tree *new_tree,- git_diff_list **diff)+ const git_diff_options *opts) {- git_iterator *a = NULL, *b = NULL;- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;-- assert(repo && old_tree && new_tree && diff);+ int error = 0; - if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||- git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0)- return -1;+ assert(diff && repo); - git__free(prefix);+ DIFF_FROM_ITERATORS(+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),+ git_iterator_for_tree(&b, new_tree, 0, pfx, pfx)+ ); - return diff_from_iterators(repo, opts, a, b, diff);+ return error; } -int git_diff_index_to_tree(+int git_diff_tree_to_index(+ git_diff_list **diff, git_repository *repo,- const git_diff_options *opts, git_tree *old_tree,- git_diff_list **diff)+ git_index *index,+ const git_diff_options *opts) {- git_iterator *a = NULL, *b = NULL;- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;+ int error = 0; - assert(repo && diff);+ assert(diff && repo); - if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||- git_iterator_for_index_range(&b, repo, prefix, prefix) < 0)- return -1;+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)+ return error; - git__free(prefix);+ DIFF_FROM_ITERATORS(+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),+ git_iterator_for_index(&b, index, 0, pfx, pfx)+ ); - return diff_from_iterators(repo, opts, a, b, diff);+ return error; } -int git_diff_workdir_to_index(+int git_diff_index_to_workdir(+ git_diff_list **diff, git_repository *repo,- const git_diff_options *opts,- git_diff_list **diff)+ git_index *index,+ const git_diff_options *opts) {- git_iterator *a = NULL, *b = NULL;- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;+ int error = 0; - assert(repo && diff);+ assert(diff && repo); - if (git_iterator_for_index_range(&a, repo, prefix, prefix) < 0 ||- git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0)- return -1;+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)+ return error; - git__free(prefix);+ DIFF_FROM_ITERATORS(+ git_iterator_for_index(&a, index, 0, pfx, pfx),+ git_iterator_for_workdir(+ &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)+ ); - return diff_from_iterators(repo, opts, a, b, diff);+ return error; } -int git_diff_workdir_to_tree(+int git_diff_tree_to_workdir(+ git_diff_list **diff, git_repository *repo,- const git_diff_options *opts, git_tree *old_tree,- git_diff_list **diff)-{- git_iterator *a = NULL, *b = NULL;- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;-- assert(repo && old_tree && diff);-- if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||- git_iterator_for_workdir_range(&b, repo, prefix, prefix) < 0)- return -1;-- git__free(prefix);-- return diff_from_iterators(repo, opts, a, b, diff);-}--int git_diff_merge(- git_diff_list *onto,- const git_diff_list *from)+ const git_diff_options *opts) { int error = 0;- git_pool onto_pool;- git_vector onto_new;- git_diff_delta *delta;- unsigned int i, j; - assert(onto && from);-- if (!from->deltas.length)- return 0;-- if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 ||- git_pool_init(&onto_pool, 1, 0) < 0)- return -1;-- for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {- git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);- const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);- int cmp = !f ? -1 : !o ? 1 : strcmp(o->old_file.path, f->old_file.path);-- if (cmp < 0) {- delta = diff_delta__dup(o, &onto_pool);- i++;- } else if (cmp > 0) {- delta = diff_delta__dup(f, &onto_pool);- j++;- } else {- delta = diff_delta__merge_like_cgit(o, f, &onto_pool);- i++;- j++;- }-- if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)- break;- }-- if (!error) {- git_vector_swap(&onto->deltas, &onto_new);- git_pool_swap(&onto->pool, &onto_pool);- onto->new_src = from->new_src;- }+ assert(diff && repo); - git_vector_foreach(&onto_new, i, delta)- git__free(delta);- git_vector_free(&onto_new);- git_pool_clear(&onto_pool);+ DIFF_FROM_ITERATORS(+ git_iterator_for_tree(&a, old_tree, 0, pfx, pfx),+ git_iterator_for_workdir(+ &b, repo, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx)+ ); return error; }-
libgit2/src/diff.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,6 +7,9 @@ #ifndef INCLUDE_diff_h__ #define INCLUDE_diff_h__ +#include "git2/diff.h"+#include "git2/oid.h"+ #include <stdio.h> #include "vector.h" #include "buffer.h"@@ -20,21 +23,56 @@ enum { GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */- GIT_DIFFCAPS_TRUST_EXEC_BIT = (1 << 2), /* use st_mode exec bit? */+ GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ }; +enum {+ GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */+ GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */+ GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */+ GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */+ GIT_DIFF_FLAG__TO_DELETE = (1 << 11), /* delete entry during rename det. */+ GIT_DIFF_FLAG__TO_SPLIT = (1 << 12), /* split entry during rename det. */+};+ struct git_diff_list {+ git_refcount rc; git_repository *repo; git_diff_options opts; git_vector pathspec;- git_vector deltas; /* vector of git_diff_file_delta */+ git_vector deltas; /* vector of git_diff_delta */ git_pool pool; git_iterator_type_t old_src; git_iterator_type_t new_src; uint32_t diffcaps;++ int (*strcomp)(const char *, const char *);+ int (*strncomp)(const char *, const char *, size_t);+ int (*pfxcomp)(const char *str, const char *pfx);+ int (*entrycomp)(const void *a, const void *b); };++extern void git_diff__cleanup_modes(+ uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);++extern void git_diff_list_addref(git_diff_list *diff);++extern int git_diff_delta__cmp(const void *a, const void *b);++extern bool git_diff_delta__should_skip(+ const git_diff_options *opts, const git_diff_delta *delta);++extern int git_diff__oid_for_file(+ git_repository *, const char *, uint16_t, git_off_t, git_oid *);++extern int git_diff__from_iterators(+ git_diff_list **diff_ptr,+ git_repository *repo,+ git_iterator *old_iter,+ git_iterator *new_iter,+ const git_diff_options *opts); #endif
libgit2/src/diff_output.c view
@@ -1,786 +1,1819 @@ /*- * Copyright (C) 2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */-#include "common.h"-#include "git2/diff.h"-#include "git2/attr.h"-#include "git2/blob.h"-#include "xdiff/xdiff.h"-#include <ctype.h>-#include "diff.h"-#include "map.h"-#include "fileops.h"-#include "filter.h"--typedef struct {- git_diff_list *diff;- void *cb_data;- git_diff_hunk_fn hunk_cb;- git_diff_data_fn line_cb;- unsigned int index;- git_diff_delta *delta;- git_diff_range range;-} diff_output_info;--static int read_next_int(const char **str, int *value)-{- const char *scan = *str;- int v = 0, digits = 0;- /* find next digit */- for (scan = *str; *scan && !isdigit(*scan); scan++);- /* parse next number */- for (; isdigit(*scan); scan++, digits++)- v = (v * 10) + (*scan - '0');- *str = scan;- *value = v;- return (digits > 0) ? 0 : -1;-}--static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len)-{- diff_output_info *info = priv;-- if (len == 1 && info->hunk_cb) {- git_diff_range range = { -1, 0, -1, 0 };- const char *scan = bufs[0].ptr;-- /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */- if (*scan != '@')- return -1;-- if (read_next_int(&scan, &range.old_start) < 0)- return -1;- if (*scan == ',' && read_next_int(&scan, &range.old_lines) < 0)- return -1;-- if (read_next_int(&scan, &range.new_start) < 0)- return -1;- if (*scan == ',' && read_next_int(&scan, &range.new_lines) < 0)- return -1;-- if (range.old_start < 0 || range.new_start < 0)- return -1;-- memcpy(&info->range, &range, sizeof(git_diff_range));-- return info->hunk_cb(- info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size);- }-- if ((len == 2 || len == 3) && info->line_cb) {- int origin;-- /* expect " "/"-"/"+", then data, then maybe newline */- origin =- (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :- (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :- GIT_DIFF_LINE_CONTEXT;-- if (info->line_cb(- info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0)- return -1;-- /* deal with adding and removing newline at EOF */- if (len == 3) {- if (origin == GIT_DIFF_LINE_ADDITION)- origin = GIT_DIFF_LINE_ADD_EOFNL;- else- origin = GIT_DIFF_LINE_DEL_EOFNL;-- return info->line_cb(- info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size);- }- }-- return 0;-}--#define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)--static int update_file_is_binary_by_attr(git_repository *repo, git_diff_file *file)-{- const char *value;- if (git_attr_get(&value, repo, 0, file->path, "diff") < 0)- return -1;-- if (GIT_ATTR_FALSE(value))- file->flags |= GIT_DIFF_FILE_BINARY;- else if (GIT_ATTR_TRUE(value))- file->flags |= GIT_DIFF_FILE_NOT_BINARY;- /* otherwise leave file->flags alone */-- return 0;-}--static void update_delta_is_binary(git_diff_delta *delta)-{- if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 ||- (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0)- delta->binary = 1;- else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 ||- (delta->new_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0)- delta->binary = 0;- /* otherwise leave delta->binary value untouched */-}--static int file_is_binary_by_attr(- git_diff_list *diff,- git_diff_delta *delta)-{- int error = 0, mirror_new;-- delta->binary = -1;-- /* make sure files are conceivably mmap-able */- if ((git_off_t)((size_t)delta->old_file.size) != delta->old_file.size ||- (git_off_t)((size_t)delta->new_file.size) != delta->new_file.size)- {- delta->old_file.flags |= GIT_DIFF_FILE_BINARY;- delta->new_file.flags |= GIT_DIFF_FILE_BINARY;- delta->binary = 1;- return 0;- }-- /* check if user is forcing us to text diff these files */- if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) {- delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY;- delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY;- delta->binary = 0;- return 0;- }-- /* check diff attribute +, -, or 0 */- if (update_file_is_binary_by_attr(diff->repo, &delta->old_file) < 0)- return -1;-- mirror_new = (delta->new_file.path == delta->old_file.path ||- strcmp(delta->new_file.path, delta->old_file.path) == 0);- if (mirror_new)- delta->new_file.flags &= (delta->old_file.flags & BINARY_DIFF_FLAGS);- else- error = update_file_is_binary_by_attr(diff->repo, &delta->new_file);-- update_delta_is_binary(delta);-- return error;-}--static int file_is_binary_by_content(- git_diff_delta *delta,- git_map *old_data,- git_map *new_data)-{- git_buf search;-- if ((delta->old_file.flags & BINARY_DIFF_FLAGS) == 0) {- search.ptr = old_data->data;- search.size = min(old_data->len, 4000);-- if (git_buf_is_binary(&search))- delta->old_file.flags |= GIT_DIFF_FILE_BINARY;- else- delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY;- }-- if ((delta->new_file.flags & BINARY_DIFF_FLAGS) == 0) {- search.ptr = new_data->data;- search.size = min(new_data->len, 4000);-- if (git_buf_is_binary(&search))- delta->new_file.flags |= GIT_DIFF_FILE_BINARY;- else- delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY;- }-- update_delta_is_binary(delta);-- /* TODO: if value != NULL, implement diff drivers */-- return 0;-}--static void setup_xdiff_options(- git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param)-{- memset(cfg, 0, sizeof(xdemitconf_t));- memset(param, 0, sizeof(xpparam_t));-- cfg->ctxlen =- (!opts || !opts->context_lines) ? 3 : opts->context_lines;- cfg->interhunkctxlen =- (!opts || !opts->interhunk_lines) ? 3 : opts->interhunk_lines;-- if (!opts)- return;-- if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE)- param->flags |= XDF_WHITESPACE_FLAGS;- if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)- param->flags |= XDF_IGNORE_WHITESPACE_CHANGE;- if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)- param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;-}--static int get_blob_content(- git_repository *repo,- const git_oid *oid,- git_map *map,- git_blob **blob)-{- if (git_oid_iszero(oid))- return 0;-- if (git_blob_lookup(blob, repo, oid) < 0)- return -1;-- map->data = (void *)git_blob_rawcontent(*blob);- map->len = git_blob_rawsize(*blob);- return 0;-}--static int get_workdir_content(- git_repository *repo,- git_diff_file *file,- git_map *map)-{- int error = 0;- git_buf path = GIT_BUF_INIT;-- if (git_buf_joinpath(&path, git_repository_workdir(repo), file->path) < 0)- return -1;-- if (S_ISLNK(file->mode)) {- ssize_t read_len;-- file->flags |= GIT_DIFF_FILE_FREE_DATA;- file->flags |= GIT_DIFF_FILE_BINARY;-- map->data = git__malloc((size_t)file->size + 1);- GITERR_CHECK_ALLOC(map->data);-- read_len = p_readlink(path.ptr, map->data, (size_t)file->size + 1);- if (read_len != (ssize_t)file->size) {- giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path);- error = -1;- } else- map->len = read_len;- }- else {- error = git_futils_mmap_ro_file(map, path.ptr);- file->flags |= GIT_DIFF_FILE_UNMAP_DATA;- }- git_buf_free(&path);- return error;-}--static void release_content(git_diff_file *file, git_map *map, git_blob *blob)-{- if (blob != NULL)- git_blob_free(blob);-- if (file->flags & GIT_DIFF_FILE_FREE_DATA) {- git__free(map->data);- map->data = NULL;- file->flags &= ~GIT_DIFF_FILE_FREE_DATA;- }- else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) {- git_futils_mmap_free(map);- map->data = NULL;- file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA;- }-}--static void fill_map_from_mmfile(git_map *dst, mmfile_t *src) {- assert(dst && src);-- dst->data = src->ptr;- dst->len = src->size;-#ifdef GIT_WIN32- dst->fmh = NULL;-#endif-}--int git_diff_foreach(- git_diff_list *diff,- void *data,- git_diff_file_fn file_cb,- git_diff_hunk_fn hunk_cb,- git_diff_data_fn line_cb)-{- int error = 0;- diff_output_info info;- git_diff_delta *delta;- xpparam_t xdiff_params;- xdemitconf_t xdiff_config;- xdemitcb_t xdiff_callback;-- info.diff = diff;- info.cb_data = data;- info.hunk_cb = hunk_cb;- info.line_cb = line_cb;-- setup_xdiff_options(&diff->opts, &xdiff_config, &xdiff_params);- memset(&xdiff_callback, 0, sizeof(xdiff_callback));- xdiff_callback.outf = diff_output_cb;- xdiff_callback.priv = &info;-- git_vector_foreach(&diff->deltas, info.index, delta) {- git_blob *old_blob = NULL, *new_blob = NULL;- git_map old_data, new_data;- mmfile_t old_xdiff_data, new_xdiff_data;-- if (delta->status == GIT_DELTA_UNMODIFIED &&- (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)- continue;-- if (delta->status == GIT_DELTA_IGNORED &&- (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)- continue;-- if (delta->status == GIT_DELTA_UNTRACKED &&- (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)- continue;-- if ((error = file_is_binary_by_attr(diff, delta)) < 0)- goto cleanup;-- old_data.data = "";- old_data.len = 0;- new_data.data = "";- new_data.len = 0;-- /* TODO: Partial blob reading to defer loading whole blob.- * I.e. I want a blob with just the first 4kb loaded, then- * later on I will read the rest of the blob if needed.- */-- /* map files */- if (delta->binary != 1 &&- (hunk_cb || line_cb) &&- (delta->status == GIT_DELTA_DELETED ||- delta->status == GIT_DELTA_MODIFIED))- {- if (diff->old_src == GIT_ITERATOR_WORKDIR)- error = get_workdir_content(diff->repo, &delta->old_file, &old_data);- else- error = get_blob_content(- diff->repo, &delta->old_file.oid, &old_data, &old_blob);-- if (error < 0)- goto cleanup;- }-- if (delta->binary != 1 &&- (hunk_cb || line_cb || git_oid_iszero(&delta->new_file.oid)) &&- (delta->status == GIT_DELTA_ADDED ||- delta->status == GIT_DELTA_MODIFIED))- {- if (diff->new_src == GIT_ITERATOR_WORKDIR)- error = get_workdir_content(diff->repo, &delta->new_file, &new_data);- else- error = get_blob_content(- diff->repo, &delta->new_file.oid, &new_data, &new_blob);-- if (error < 0)- goto cleanup;-- if ((delta->new_file.flags | GIT_DIFF_FILE_VALID_OID) == 0) {- error = git_odb_hash(- &delta->new_file.oid, new_data.data, new_data.len, GIT_OBJ_BLOB);-- if (error < 0)- goto cleanup;-- /* since we did not have the definitive oid, we may have- * incorrect status and need to skip this item.- */- if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) {- delta->status = GIT_DELTA_UNMODIFIED;- if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)- goto cleanup;- }- }- }-- /* if we have not already decided whether file is binary,- * check the first 4K for nul bytes to decide...- */- if (delta->binary == -1) {- error = file_is_binary_by_content(- delta, &old_data, &new_data);- if (error < 0)- goto cleanup;- }-- /* TODO: if ignore_whitespace is set, then we *must* do text- * diffs to tell if a file has really been changed.- */-- if (file_cb != NULL) {- error = file_cb(data, delta, (float)info.index / diff->deltas.length);- if (error < 0)- goto cleanup;- }-- /* don't do hunk and line diffs if file is binary */- if (delta->binary == 1)- goto cleanup;-- /* nothing to do if we did not get data */- if (!old_data.len && !new_data.len)- goto cleanup;-- assert(hunk_cb || line_cb);-- info.delta = delta;- old_xdiff_data.ptr = old_data.data;- old_xdiff_data.size = old_data.len;- new_xdiff_data.ptr = new_data.data;- new_xdiff_data.size = new_data.len;-- xdl_diff(&old_xdiff_data, &new_xdiff_data,- &xdiff_params, &xdiff_config, &xdiff_callback);--cleanup:- release_content(&delta->old_file, &old_data, old_blob);- release_content(&delta->new_file, &new_data, new_blob);-- if (error < 0)- break;- }-- return error;-}---typedef struct {- git_diff_list *diff;- git_diff_data_fn print_cb;- void *cb_data;- git_buf *buf;-} diff_print_info;--static char pick_suffix(int mode)-{- if (S_ISDIR(mode))- return '/';- else if (mode & 0100)- /* in git, modes are very regular, so we must have 0100755 mode */- return '*';- else- return ' ';-}--static int print_compact(void *data, git_diff_delta *delta, float progress)-{- diff_print_info *pi = data;- char code, old_suffix, new_suffix;-- GIT_UNUSED(progress);-- switch (delta->status) {- case GIT_DELTA_ADDED: code = 'A'; break;- case GIT_DELTA_DELETED: code = 'D'; break;- case GIT_DELTA_MODIFIED: code = 'M'; break;- case GIT_DELTA_RENAMED: code = 'R'; break;- case GIT_DELTA_COPIED: code = 'C'; break;- case GIT_DELTA_IGNORED: code = 'I'; break;- case GIT_DELTA_UNTRACKED: code = '?'; break;- default: code = 0;- }-- if (!code)- return 0;-- old_suffix = pick_suffix(delta->old_file.mode);- new_suffix = pick_suffix(delta->new_file.mode);-- git_buf_clear(pi->buf);-- if (delta->old_file.path != delta->new_file.path &&- strcmp(delta->old_file.path,delta->new_file.path) != 0)- git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,- delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);- else if (delta->old_file.mode != delta->new_file.mode &&- delta->old_file.mode != 0 && delta->new_file.mode != 0)- git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code,- delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode);- else if (old_suffix != ' ')- git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);- else- git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path);-- if (git_buf_oom(pi->buf))- return -1;-- return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf));-}--int git_diff_print_compact(- git_diff_list *diff,- void *cb_data,- git_diff_data_fn print_cb)-{- int error;- git_buf buf = GIT_BUF_INIT;- diff_print_info pi;-- pi.diff = diff;- pi.print_cb = print_cb;- pi.cb_data = cb_data;- pi.buf = &buf;-- error = git_diff_foreach(diff, &pi, print_compact, NULL, NULL);-- git_buf_free(&buf);-- return error;-}---static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)-{- char start_oid[8], end_oid[8];-- /* TODO: Determine a good actual OID range to print */- git_oid_tostr(start_oid, sizeof(start_oid), &delta->old_file.oid);- git_oid_tostr(end_oid, sizeof(end_oid), &delta->new_file.oid);-- /* TODO: Match git diff more closely */- if (delta->old_file.mode == delta->new_file.mode) {- git_buf_printf(pi->buf, "index %s..%s %o\n",- start_oid, end_oid, delta->old_file.mode);- } else {- if (delta->old_file.mode == 0) {- git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode);- } else if (delta->new_file.mode == 0) {- git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode);- } else {- git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode);- git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode);- }- git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid);- }-- if (git_buf_oom(pi->buf))- return -1;-- return 0;-}--static int print_patch_file(void *data, git_diff_delta *delta, float progress)-{- diff_print_info *pi = data;- const char *oldpfx = pi->diff->opts.old_prefix;- const char *oldpath = delta->old_file.path;- const char *newpfx = pi->diff->opts.new_prefix;- const char *newpath = delta->new_file.path;- int result;-- GIT_UNUSED(progress);-- if (!oldpfx)- oldpfx = DIFF_OLD_PREFIX_DEFAULT;-- if (!newpfx)- newpfx = DIFF_NEW_PREFIX_DEFAULT;-- git_buf_clear(pi->buf);- git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path);-- if (print_oid_range(pi, delta) < 0)- return -1;-- if (git_oid_iszero(&delta->old_file.oid)) {- oldpfx = "";- oldpath = "/dev/null";- }- if (git_oid_iszero(&delta->new_file.oid)) {- newpfx = "";- newpath = "/dev/null";- }-- if (delta->binary != 1) {- git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);- git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);- }-- if (git_buf_oom(pi->buf))- return -1;-- result = pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf));- if (result < 0)- return result;-- if (delta->binary != 1)- return 0;-- git_buf_clear(pi->buf);- git_buf_printf(- pi->buf, "Binary files %s%s and %s%s differ\n",- oldpfx, oldpath, newpfx, newpath);- if (git_buf_oom(pi->buf))- return -1;-- return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_BINARY, git_buf_cstr(pi->buf), git_buf_len(pi->buf));-}--static int print_patch_hunk(- void *data,- git_diff_delta *d,- git_diff_range *r,- const char *header,- size_t header_len)-{- diff_print_info *pi = data;-- git_buf_clear(pi->buf);- if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0)- return -1;-- return pi->print_cb(pi->cb_data, d, r, GIT_DIFF_LINE_HUNK_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf));-}--static int print_patch_line(- void *data,- git_diff_delta *delta,- git_diff_range *range,- char line_origin, /* GIT_DIFF_LINE value from above */- const char *content,- size_t content_len)-{- diff_print_info *pi = data;-- git_buf_clear(pi->buf);-- if (line_origin == GIT_DIFF_LINE_ADDITION ||- line_origin == GIT_DIFF_LINE_DELETION ||- line_origin == GIT_DIFF_LINE_CONTEXT)- git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content);- else if (content_len > 0)- git_buf_printf(pi->buf, "%.*s", (int)content_len, content);-- if (git_buf_oom(pi->buf))- return -1;-- return pi->print_cb(pi->cb_data, delta, range, line_origin, git_buf_cstr(pi->buf), git_buf_len(pi->buf));-}--int git_diff_print_patch(- git_diff_list *diff,- void *cb_data,- git_diff_data_fn print_cb)-{- int error;- git_buf buf = GIT_BUF_INIT;- diff_print_info pi;-- pi.diff = diff;- pi.print_cb = print_cb;- pi.cb_data = cb_data;- pi.buf = &buf;-- error = git_diff_foreach(- diff, &pi, print_patch_file, print_patch_hunk, print_patch_line);-- git_buf_free(&buf);-- return error;-}--int git_diff_blobs(- git_blob *old_blob,- git_blob *new_blob,- git_diff_options *options,- void *cb_data,- git_diff_file_fn file_cb,- git_diff_hunk_fn hunk_cb,- git_diff_data_fn line_cb)-{- diff_output_info info;- git_diff_delta delta;- mmfile_t old_data, new_data;- git_map old_map, new_map;- xpparam_t xdiff_params;- xdemitconf_t xdiff_config;- xdemitcb_t xdiff_callback;- git_blob *new, *old;-- memset(&delta, 0, sizeof(delta));-- new = new_blob;- old = old_blob;-- if (options && (options->flags & GIT_DIFF_REVERSE)) {- git_blob *swap = old;- old = new;- new = swap;- }-- if (old) {- old_data.ptr = (char *)git_blob_rawcontent(old);- old_data.size = git_blob_rawsize(old);- git_oid_cpy(&delta.old_file.oid, git_object_id((const git_object *)old));- } else {- old_data.ptr = "";- old_data.size = 0;- }-- if (new) {- new_data.ptr = (char *)git_blob_rawcontent(new);- new_data.size = git_blob_rawsize(new);- git_oid_cpy(&delta.new_file.oid, git_object_id((const git_object *)new));- } else {- new_data.ptr = "";- new_data.size = 0;- }-- /* populate a "fake" delta record */- delta.status = new ?- (old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :- (old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);-- if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0)- delta.status = GIT_DELTA_UNMODIFIED;-- delta.old_file.size = old_data.size;- delta.new_file.size = new_data.size;-- fill_map_from_mmfile(&old_map, &old_data);- fill_map_from_mmfile(&new_map, &new_data);-- if (file_is_binary_by_content(&delta, &old_map, &new_map) < 0)- return -1;-- if (file_cb != NULL) {- int error = file_cb(cb_data, &delta, 1);- if (error < 0)- return error;- }-- /* don't do hunk and line diffs if the two blobs are identical */- if (delta.status == GIT_DELTA_UNMODIFIED)- return 0;-- /* don't do hunk and line diffs if file is binary */- if (delta.binary == 1)- return 0;-- info.diff = NULL;- info.delta = δ- info.cb_data = cb_data;- info.hunk_cb = hunk_cb;- info.line_cb = line_cb;-- setup_xdiff_options(options, &xdiff_config, &xdiff_params);- memset(&xdiff_callback, 0, sizeof(xdiff_callback));- xdiff_callback.outf = diff_output_cb;- xdiff_callback.priv = &info;-- xdl_diff(&old_data, &new_data, &xdiff_params, &xdiff_config, &xdiff_callback);+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#include "common.h"+#include "git2/attr.h"+#include "git2/oid.h"+#include "git2/submodule.h"+#include "diff_output.h"+#include <ctype.h>+#include "fileops.h"+#include "filter.h"+#include "buf_text.h"++static int read_next_int(const char **str, int *value)+{+ const char *scan = *str;+ int v = 0, digits = 0;+ /* find next digit */+ for (scan = *str; *scan && !isdigit(*scan); scan++);+ /* parse next number */+ for (; isdigit(*scan); scan++, digits++)+ v = (v * 10) + (*scan - '0');+ *str = scan;+ *value = v;+ return (digits > 0) ? 0 : -1;+}++static int parse_hunk_header(git_diff_range *range, const char *header)+{+ /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */+ if (*header != '@')+ return -1;+ if (read_next_int(&header, &range->old_start) < 0)+ return -1;+ if (*header == ',') {+ if (read_next_int(&header, &range->old_lines) < 0)+ return -1;+ } else+ range->old_lines = 1;+ if (read_next_int(&header, &range->new_start) < 0)+ return -1;+ if (*header == ',') {+ if (read_next_int(&header, &range->new_lines) < 0)+ return -1;+ } else+ range->new_lines = 1;+ if (range->old_start < 0 || range->new_start < 0)+ return -1;++ return 0;+}++#define KNOWN_BINARY_FLAGS (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)+#define NOT_BINARY_FLAGS (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)++static int update_file_is_binary_by_attr(+ git_repository *repo, git_diff_file *file)+{+ const char *value;++ /* because of blob diffs, cannot assume path is set */+ if (!file->path || !strlen(file->path))+ return 0;++ if (git_attr_get(&value, repo, 0, file->path, "diff") < 0)+ return -1;++ if (GIT_ATTR_FALSE(value))+ file->flags |= GIT_DIFF_FLAG_BINARY;+ else if (GIT_ATTR_TRUE(value))+ file->flags |= GIT_DIFF_FLAG_NOT_BINARY;+ /* otherwise leave file->flags alone */++ return 0;+}++static void update_delta_is_binary(git_diff_delta *delta)+{+ if ((delta->old_file.flags & GIT_DIFF_FLAG_BINARY) != 0 ||+ (delta->new_file.flags & GIT_DIFF_FLAG_BINARY) != 0)+ delta->flags |= GIT_DIFF_FLAG_BINARY;++ else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 &&+ (delta->new_file.flags & NOT_BINARY_FLAGS) != 0)+ delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;++ /* otherwise leave delta->flags binary value untouched */+}++/* returns if we forced binary setting (and no further checks needed) */+static bool diff_delta_is_binary_forced(+ diff_context *ctxt,+ git_diff_delta *delta)+{+ /* return true if binary-ness has already been settled */+ if ((delta->flags & KNOWN_BINARY_FLAGS) != 0)+ return true;++ /* make sure files are conceivably mmap-able */+ if ((git_off_t)((size_t)delta->old_file.size) != delta->old_file.size ||+ (git_off_t)((size_t)delta->new_file.size) != delta->new_file.size)+ {+ delta->old_file.flags |= GIT_DIFF_FLAG_BINARY;+ delta->new_file.flags |= GIT_DIFF_FLAG_BINARY;+ delta->flags |= GIT_DIFF_FLAG_BINARY;+ return true;+ }++ /* check if user is forcing us to text diff these files */+ if (ctxt->opts && (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) != 0) {+ delta->old_file.flags |= GIT_DIFF_FLAG_NOT_BINARY;+ delta->new_file.flags |= GIT_DIFF_FLAG_NOT_BINARY;+ delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;+ return true;+ }++ return false;+}++static int diff_delta_is_binary_by_attr(+ diff_context *ctxt, git_diff_patch *patch)+{+ int error = 0, mirror_new;+ git_diff_delta *delta = patch->delta;++ if (diff_delta_is_binary_forced(ctxt, delta))+ return 0;++ /* check diff attribute +, -, or 0 */+ if (update_file_is_binary_by_attr(ctxt->repo, &delta->old_file) < 0)+ return -1;++ mirror_new = (delta->new_file.path == delta->old_file.path ||+ ctxt->diff->strcomp(delta->new_file.path, delta->old_file.path) == 0);+ if (mirror_new)+ delta->new_file.flags |= (delta->old_file.flags & KNOWN_BINARY_FLAGS);+ else+ error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file);++ update_delta_is_binary(delta);++ return error;+}++static int diff_delta_is_binary_by_content(+ diff_context *ctxt,+ git_diff_delta *delta,+ git_diff_file *file,+ const git_map *map)+{+ const git_buf search = { map->data, 0, min(map->len, 4000) };++ if (diff_delta_is_binary_forced(ctxt, delta))+ return 0;++ /* TODO: provide encoding / binary detection callbacks that can+ * be UTF-8 aware, etc. For now, instead of trying to be smart,+ * let's just use the simple NUL-byte detection that core git uses.+ */++ /* previously was: if (git_buf_text_is_binary(&search)) */+ if (git_buf_text_contains_nul(&search))+ file->flags |= GIT_DIFF_FLAG_BINARY;+ else+ file->flags |= GIT_DIFF_FLAG_NOT_BINARY;++ update_delta_is_binary(delta);++ return 0;+}++static int diff_delta_is_binary_by_size(+ diff_context *ctxt, git_diff_delta *delta, git_diff_file *file)+{+ git_off_t threshold = MAX_DIFF_FILESIZE;++ if ((file->flags & KNOWN_BINARY_FLAGS) != 0)+ return 0;++ if (ctxt && ctxt->opts) {+ if (ctxt->opts->max_size < 0)+ return 0;++ if (ctxt->opts->max_size > 0)+ threshold = ctxt->opts->max_size;+ }++ if (file->size > threshold)+ file->flags |= GIT_DIFF_FLAG_BINARY;++ update_delta_is_binary(delta);++ return 0;+}++static void setup_xdiff_options(+ const git_diff_options *opts, xdemitconf_t *cfg, xpparam_t *param)+{+ memset(cfg, 0, sizeof(xdemitconf_t));+ memset(param, 0, sizeof(xpparam_t));++ cfg->ctxlen =+ (!opts) ? 3 : opts->context_lines;+ cfg->interhunkctxlen =+ (!opts) ? 0 : opts->interhunk_lines;++ if (!opts)+ return;++ if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE)+ param->flags |= XDF_WHITESPACE_FLAGS;+ if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)+ param->flags |= XDF_IGNORE_WHITESPACE_CHANGE;+ if (opts->flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)+ param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;+}+++static int get_blob_content(+ diff_context *ctxt,+ git_diff_delta *delta,+ git_diff_file *file,+ git_map *map,+ git_blob **blob)+{+ int error;+ git_odb_object *odb_obj = NULL;++ if (git_oid_iszero(&file->oid))+ return 0;++ if (file->mode == GIT_FILEMODE_COMMIT)+ {+ char oidstr[GIT_OID_HEXSZ+1];+ git_buf content = GIT_BUF_INIT;++ git_oid_fmt(oidstr, &file->oid);+ oidstr[GIT_OID_HEXSZ] = 0;+ git_buf_printf(&content, "Subproject commit %s\n", oidstr );++ map->data = git_buf_detach(&content);+ map->len = strlen(map->data);++ file->flags |= GIT_DIFF_FLAG__FREE_DATA;+ return 0;+ }++ if (!file->size) {+ git_odb *odb;+ size_t len;+ git_otype type;++ /* peek at object header to avoid loading if too large */+ if ((error = git_repository_odb__weakptr(&odb, ctxt->repo)) < 0 ||+ (error = git_odb__read_header_or_object(+ &odb_obj, &len, &type, odb, &file->oid)) < 0)+ return error;++ assert(type == GIT_OBJ_BLOB);++ file->size = len;+ }++ /* if blob is too large to diff, mark as binary */+ if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0)+ return error;+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)+ return 0;++ if (odb_obj != NULL) {+ error = git_object__from_odb_object(+ (git_object **)blob, ctxt->repo, odb_obj, GIT_OBJ_BLOB);+ git_odb_object_free(odb_obj);+ } else+ error = git_blob_lookup(blob, ctxt->repo, &file->oid);++ if (error)+ return error;++ map->data = (void *)git_blob_rawcontent(*blob);+ map->len = (size_t)git_blob_rawsize(*blob);++ return diff_delta_is_binary_by_content(ctxt, delta, file, map);+}++static int get_workdir_sm_content(+ diff_context *ctxt,+ git_diff_file *file,+ git_map *map)+{+ int error = 0;+ git_buf content = GIT_BUF_INIT;+ git_submodule* sm = NULL;+ unsigned int sm_status = 0;+ const char* sm_status_text = "";+ char oidstr[GIT_OID_HEXSZ+1];++ if ((error = git_submodule_lookup(&sm, ctxt->repo, file->path)) < 0 ||+ (error = git_submodule_status(&sm_status, sm)) < 0)+ {+ /* GIT_EEXISTS means a "submodule" that has not been git added */+ if (error == GIT_EEXISTS)+ error = 0;+ return error;+ }++ /* update OID if we didn't have it previously */+ if ((file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) {+ const git_oid* sm_head;++ if ((sm_head = git_submodule_wd_id(sm)) != NULL ||+ (sm_head = git_submodule_head_id(sm)) != NULL)+ {+ git_oid_cpy(&file->oid, sm_head);+ file->flags |= GIT_DIFF_FLAG_VALID_OID;+ }+ }++ git_oid_fmt(oidstr, &file->oid);+ oidstr[GIT_OID_HEXSZ] = '\0';++ if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))+ sm_status_text = "-dirty";++ git_buf_printf(&content, "Subproject commit %s%s\n",+ oidstr, sm_status_text);++ map->data = git_buf_detach(&content);+ map->len = strlen(map->data);++ file->flags |= GIT_DIFF_FLAG__FREE_DATA;++ return 0;+}++static int get_filtered(+ git_map *map, git_file fd, git_diff_file *file, git_vector *filters)+{+ int error;+ git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT;++ if ((error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) < 0)+ return error;++ if (!filters->length)+ git_buf_swap(&filtered, &raw);+ else+ error = git_filters_apply(&filtered, &raw, filters);++ if (!error) {+ map->len = git_buf_len(&filtered);+ map->data = git_buf_detach(&filtered);++ file->flags |= GIT_DIFF_FLAG__FREE_DATA;+ }++ git_buf_free(&raw);+ git_buf_free(&filtered);++ return error;+}++static int get_workdir_content(+ diff_context *ctxt,+ git_diff_delta *delta,+ git_diff_file *file,+ git_map *map)+{+ int error = 0;+ git_buf path = GIT_BUF_INIT;+ const char *wd = git_repository_workdir(ctxt->repo);++ if (S_ISGITLINK(file->mode))+ return get_workdir_sm_content(ctxt, file, map);++ if (S_ISDIR(file->mode))+ return 0;++ if (git_buf_joinpath(&path, wd, file->path) < 0)+ return -1;++ if (S_ISLNK(file->mode)) {+ ssize_t alloc_len, read_len;++ file->flags |= GIT_DIFF_FLAG__FREE_DATA;+ file->flags |= GIT_DIFF_FLAG_BINARY;++ /* link path on disk could be UTF-16, so prepare a buffer that is+ * big enough to handle some UTF-8 data expansion+ */+ alloc_len = (ssize_t)(file->size * 2) + 1;++ map->data = git__malloc(alloc_len);+ GITERR_CHECK_ALLOC(map->data);++ read_len = p_readlink(path.ptr, map->data, alloc_len);+ if (read_len < 0) {+ giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path);+ error = -1;+ goto cleanup;+ }++ map->len = read_len;+ }+ else {+ git_file fd = git_futils_open_ro(path.ptr);+ git_vector filters = GIT_VECTOR_INIT;++ if (fd < 0) {+ error = fd;+ goto cleanup;+ }++ if (!file->size && !(file->size = git_futils_filesize(fd)))+ goto close_and_cleanup;++ if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0 ||+ (delta->flags & GIT_DIFF_FLAG_BINARY) != 0)+ goto close_and_cleanup;++ error = git_filters_load(+ &filters, ctxt->repo, file->path, GIT_FILTER_TO_ODB);+ if (error < 0)+ goto close_and_cleanup;++ if (error == 0) { /* note: git_filters_load returns filter count */+ error = git_futils_mmap_ro(map, fd, 0, (size_t)file->size);+ if (!error)+ file->flags |= GIT_DIFF_FLAG__UNMAP_DATA;+ }+ if (error != 0)+ error = get_filtered(map, fd, file, &filters);++close_and_cleanup:+ git_filters_free(&filters);+ p_close(fd);+ }++ /* once data is loaded, update OID if we didn't have it previously */+ if (!error && (file->flags & GIT_DIFF_FLAG_VALID_OID) == 0) {+ error = git_odb_hash(+ &file->oid, map->data, map->len, GIT_OBJ_BLOB);+ if (!error)+ file->flags |= GIT_DIFF_FLAG_VALID_OID;+ }++ if (!error)+ error = diff_delta_is_binary_by_content(ctxt, delta, file, map);++cleanup:+ git_buf_free(&path);+ return error;+}++static void release_content(git_diff_file *file, git_map *map, git_blob *blob)+{+ if (blob != NULL)+ git_blob_free(blob);++ if (file->flags & GIT_DIFF_FLAG__FREE_DATA) {+ git__free(map->data);+ map->data = "";+ map->len = 0;+ file->flags &= ~GIT_DIFF_FLAG__FREE_DATA;+ }+ else if (file->flags & GIT_DIFF_FLAG__UNMAP_DATA) {+ git_futils_mmap_free(map);+ map->data = "";+ map->len = 0;+ file->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA;+ }+}+++static int diff_context_init(+ diff_context *ctxt,+ git_diff_list *diff,+ git_repository *repo,+ const git_diff_options *opts,+ git_diff_file_cb file_cb,+ git_diff_hunk_cb hunk_cb,+ git_diff_data_cb data_cb,+ void *payload)+{+ memset(ctxt, 0, sizeof(diff_context));++ if (!repo && diff)+ repo = diff->repo;++ if (!opts && diff)+ opts = &diff->opts;++ ctxt->repo = repo;+ ctxt->diff = diff;+ ctxt->opts = opts;+ ctxt->file_cb = file_cb;+ ctxt->hunk_cb = hunk_cb;+ ctxt->data_cb = data_cb;+ ctxt->payload = payload;+ ctxt->error = 0;++ setup_xdiff_options(ctxt->opts, &ctxt->xdiff_config, &ctxt->xdiff_params);++ return 0;+}++static int diff_delta_file_callback(+ diff_context *ctxt, git_diff_delta *delta, size_t idx)+{+ float progress;++ if (!ctxt->file_cb)+ return 0;++ progress = ctxt->diff ? ((float)idx / ctxt->diff->deltas.length) : 1.0f;++ if (ctxt->file_cb(delta, progress, ctxt->payload) != 0)+ ctxt->error = GIT_EUSER;++ return ctxt->error;+}++static void diff_patch_init(+ diff_context *ctxt, git_diff_patch *patch)+{+ memset(patch, 0, sizeof(git_diff_patch));++ patch->diff = ctxt->diff;+ patch->ctxt = ctxt;++ if (patch->diff) {+ patch->old_src = patch->diff->old_src;+ patch->new_src = patch->diff->new_src;+ } else {+ patch->old_src = patch->new_src = GIT_ITERATOR_TYPE_TREE;+ }+}++static git_diff_patch *diff_patch_alloc(+ diff_context *ctxt, git_diff_delta *delta)+{+ git_diff_patch *patch = git__malloc(sizeof(git_diff_patch));+ if (!patch)+ return NULL;++ diff_patch_init(ctxt, patch);++ git_diff_list_addref(patch->diff);++ GIT_REFCOUNT_INC(patch);++ patch->delta = delta;+ patch->flags = GIT_DIFF_PATCH_ALLOCATED;++ return patch;+}++static int diff_patch_load(+ diff_context *ctxt, git_diff_patch *patch)+{+ int error = 0;+ git_diff_delta *delta = patch->delta;+ bool check_if_unmodified = false;++ if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)+ return 0;++ error = diff_delta_is_binary_by_attr(ctxt, patch);++ patch->old_data.data = "";+ patch->old_data.len = 0;+ patch->old_blob = NULL;++ patch->new_data.data = "";+ patch->new_data.len = 0;+ patch->new_blob = NULL;++ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)+ goto cleanup;++ if (!ctxt->hunk_cb &&+ !ctxt->data_cb &&+ (ctxt->opts->flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)+ goto cleanup;++ switch (delta->status) {+ case GIT_DELTA_ADDED:+ delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;+ break;+ case GIT_DELTA_DELETED:+ delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;+ break;+ case GIT_DELTA_MODIFIED:+ break;+ case GIT_DELTA_UNTRACKED:+ delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;+ if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0)+ delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;+ break;+ default:+ delta->new_file.flags |= GIT_DIFF_FLAG__NO_DATA;+ delta->old_file.flags |= GIT_DIFF_FLAG__NO_DATA;+ break;+ }++#define CHECK_UNMODIFIED (GIT_DIFF_FLAG__NO_DATA | GIT_DIFF_FLAG_VALID_OID)++ check_if_unmodified =+ (delta->old_file.flags & CHECK_UNMODIFIED) == 0 &&+ (delta->new_file.flags & CHECK_UNMODIFIED) == 0;++ /* Always try to load workdir content first, since it may need to be+ * filtered (and hence use 2x memory) and we want to minimize the max+ * memory footprint during diff.+ */++ if ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&+ patch->old_src == GIT_ITERATOR_TYPE_WORKDIR) {+ if ((error = get_workdir_content(+ ctxt, delta, &delta->old_file, &patch->old_data)) < 0)+ goto cleanup;+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)+ goto cleanup;+ }++ if ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&+ patch->new_src == GIT_ITERATOR_TYPE_WORKDIR) {+ if ((error = get_workdir_content(+ ctxt, delta, &delta->new_file, &patch->new_data)) < 0)+ goto cleanup;+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)+ goto cleanup;+ }++ if ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&+ patch->old_src != GIT_ITERATOR_TYPE_WORKDIR) {+ if ((error = get_blob_content(+ ctxt, delta, &delta->old_file,+ &patch->old_data, &patch->old_blob)) < 0)+ goto cleanup;+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)+ goto cleanup;+ }++ if ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0 &&+ patch->new_src != GIT_ITERATOR_TYPE_WORKDIR) {+ if ((error = get_blob_content(+ ctxt, delta, &delta->new_file,+ &patch->new_data, &patch->new_blob)) < 0)+ goto cleanup;+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)+ goto cleanup;+ }++ /* if we did not previously have the definitive oid, we may have+ * incorrect status and need to switch this to UNMODIFIED.+ */+ if (check_if_unmodified &&+ delta->old_file.mode == delta->new_file.mode &&+ !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))+ {+ delta->status = GIT_DELTA_UNMODIFIED;++ if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)+ goto cleanup;+ }++cleanup:+ if ((delta->flags & KNOWN_BINARY_FLAGS) == 0)+ update_delta_is_binary(delta);++ if (!error) {+ patch->flags |= GIT_DIFF_PATCH_LOADED;++ /* patch is diffable only for non-binary, modified files where at+ * least one side has data and there is actual change in the data+ */+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&+ delta->status != GIT_DELTA_UNMODIFIED &&+ (patch->old_data.len || patch->new_data.len) &&+ (patch->old_data.len != patch->new_data.len ||+ !git_oid_equal(&delta->old_file.oid, &delta->new_file.oid)))+ patch->flags |= GIT_DIFF_PATCH_DIFFABLE;+ }++ return error;+}++static int diff_patch_cb(void *priv, mmbuffer_t *bufs, int len)+{+ git_diff_patch *patch = priv;+ diff_context *ctxt = patch->ctxt;++ if (len == 1) {+ ctxt->error = parse_hunk_header(&ctxt->range, bufs[0].ptr);+ if (ctxt->error < 0)+ return ctxt->error;++ if (ctxt->hunk_cb != NULL &&+ ctxt->hunk_cb(patch->delta, &ctxt->range,+ bufs[0].ptr, bufs[0].size, ctxt->payload))+ ctxt->error = GIT_EUSER;+ }++ if (len == 2 || len == 3) {+ /* expect " "/"-"/"+", then data */+ char origin =+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :+ GIT_DIFF_LINE_CONTEXT;++ if (ctxt->data_cb != NULL &&+ ctxt->data_cb(patch->delta, &ctxt->range,+ origin, bufs[1].ptr, bufs[1].size, ctxt->payload))+ ctxt->error = GIT_EUSER;+ }++ if (len == 3 && !ctxt->error) {+ /* If we have a '+' and a third buf, then we have added a line+ * without a newline and the old code had one, so DEL_EOFNL.+ * If we have a '-' and a third buf, then we have removed a line+ * with out a newline but added a blank line, so ADD_EOFNL.+ */+ char origin =+ (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :+ (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :+ GIT_DIFF_LINE_CONTEXT;++ if (ctxt->data_cb != NULL &&+ ctxt->data_cb(patch->delta, &ctxt->range,+ origin, bufs[2].ptr, bufs[2].size, ctxt->payload))+ ctxt->error = GIT_EUSER;+ }++ return ctxt->error;+}++static int diff_patch_generate(+ diff_context *ctxt, git_diff_patch *patch)+{+ int error = 0;+ xdemitcb_t xdiff_callback;+ mmfile_t old_xdiff_data, new_xdiff_data;++ if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)+ return 0;++ if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0)+ if ((error = diff_patch_load(ctxt, patch)) < 0)+ return error;++ if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)+ return 0;++ if (!ctxt->file_cb && !ctxt->hunk_cb)+ return 0;++ patch->ctxt = ctxt;++ memset(&xdiff_callback, 0, sizeof(xdiff_callback));+ xdiff_callback.outf = diff_patch_cb;+ xdiff_callback.priv = patch;++ old_xdiff_data.ptr = patch->old_data.data;+ old_xdiff_data.size = patch->old_data.len;+ new_xdiff_data.ptr = patch->new_data.data;+ new_xdiff_data.size = patch->new_data.len;++ xdl_diff(&old_xdiff_data, &new_xdiff_data,+ &ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback);++ error = ctxt->error;++ if (!error)+ patch->flags |= GIT_DIFF_PATCH_DIFFED;++ return error;+}++static void diff_patch_unload(git_diff_patch *patch)+{+ if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) {+ patch->flags = (patch->flags & ~GIT_DIFF_PATCH_DIFFED);++ patch->hunks_size = 0;+ patch->lines_size = 0;+ }++ if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) {+ patch->flags = (patch->flags & ~GIT_DIFF_PATCH_LOADED);++ release_content(+ &patch->delta->old_file, &patch->old_data, patch->old_blob);+ release_content(+ &patch->delta->new_file, &patch->new_data, patch->new_blob);+ }+}++static void diff_patch_free(git_diff_patch *patch)+{+ diff_patch_unload(patch);++ git__free(patch->lines);+ patch->lines = NULL;+ patch->lines_asize = 0;++ git__free(patch->hunks);+ patch->hunks = NULL;+ patch->hunks_asize = 0;++ if (!(patch->flags & GIT_DIFF_PATCH_ALLOCATED))+ return;++ patch->flags = 0;++ git_diff_list_free(patch->diff); /* decrements refcount */++ git__free(patch);+}++#define MAX_HUNK_STEP 128+#define MIN_HUNK_STEP 8+#define MAX_LINE_STEP 256+#define MIN_LINE_STEP 8++static int diff_patch_hunk_cb(+ const git_diff_delta *delta,+ const git_diff_range *range,+ const char *header,+ size_t header_len,+ void *payload)+{+ git_diff_patch *patch = payload;+ diff_patch_hunk *hunk;++ GIT_UNUSED(delta);++ if (patch->hunks_size >= patch->hunks_asize) {+ size_t new_size;+ diff_patch_hunk *new_hunks;++ if (patch->hunks_asize > MAX_HUNK_STEP)+ new_size = patch->hunks_asize + MAX_HUNK_STEP;+ else+ new_size = patch->hunks_asize * 2;+ if (new_size < MIN_HUNK_STEP)+ new_size = MIN_HUNK_STEP;++ new_hunks = git__realloc(+ patch->hunks, new_size * sizeof(diff_patch_hunk));+ if (!new_hunks)+ return -1;++ patch->hunks = new_hunks;+ patch->hunks_asize = new_size;+ }++ hunk = &patch->hunks[patch->hunks_size++];++ memcpy(&hunk->range, range, sizeof(hunk->range));++ assert(header_len + 1 < sizeof(hunk->header));+ memcpy(&hunk->header, header, header_len);+ hunk->header[header_len] = '\0';+ hunk->header_len = header_len;++ hunk->line_start = patch->lines_size;+ hunk->line_count = 0;++ patch->oldno = range->old_start;+ patch->newno = range->new_start;++ return 0;+}++static int diff_patch_line_cb(+ const git_diff_delta *delta,+ const git_diff_range *range,+ char line_origin,+ const char *content,+ size_t content_len,+ void *payload)+{+ git_diff_patch *patch = payload;+ diff_patch_hunk *hunk;+ diff_patch_line *line;++ GIT_UNUSED(delta);+ GIT_UNUSED(range);++ assert(patch->hunks_size > 0);+ assert(patch->hunks != NULL);++ hunk = &patch->hunks[patch->hunks_size - 1];++ if (patch->lines_size >= patch->lines_asize) {+ size_t new_size;+ diff_patch_line *new_lines;++ if (patch->lines_asize > MAX_LINE_STEP)+ new_size = patch->lines_asize + MAX_LINE_STEP;+ else+ new_size = patch->lines_asize * 2;+ if (new_size < MIN_LINE_STEP)+ new_size = MIN_LINE_STEP;++ new_lines = git__realloc(+ patch->lines, new_size * sizeof(diff_patch_line));+ if (!new_lines)+ return -1;++ patch->lines = new_lines;+ patch->lines_asize = new_size;+ }++ line = &patch->lines[patch->lines_size++];++ line->ptr = content;+ line->len = content_len;+ line->origin = line_origin;++ /* do some bookkeeping so we can provide old/new line numbers */++ for (line->lines = 0; content_len > 0; --content_len) {+ if (*content++ == '\n')+ ++line->lines;+ }++ switch (line_origin) {+ case GIT_DIFF_LINE_ADDITION:+ line->oldno = -1;+ line->newno = patch->newno;+ patch->newno += line->lines;+ break;+ case GIT_DIFF_LINE_DELETION:+ line->oldno = patch->oldno;+ line->newno = -1;+ patch->oldno += line->lines;+ break;+ default:+ line->oldno = patch->oldno;+ line->newno = patch->newno;+ patch->oldno += line->lines;+ patch->newno += line->lines;+ break;+ }++ hunk->line_count++;++ return 0;+}++static int diff_required(git_diff_list *diff, const char *action)+{+ if (!diff) {+ giterr_set(GITERR_INVALID, "Must provide valid diff to %s", action);+ return -1;+ }++ return 0;+}++int git_diff_foreach(+ git_diff_list *diff,+ git_diff_file_cb file_cb,+ git_diff_hunk_cb hunk_cb,+ git_diff_data_cb data_cb,+ void *payload)+{+ int error = 0;+ diff_context ctxt;+ size_t idx;+ git_diff_patch patch;++ if (diff_required(diff, "git_diff_foreach") < 0)+ return -1;++ if (diff_context_init(+ &ctxt, diff, NULL, NULL, file_cb, hunk_cb, data_cb, payload) < 0)+ return -1;++ diff_patch_init(&ctxt, &patch);++ git_vector_foreach(&diff->deltas, idx, patch.delta) {++ /* check flags against patch status */+ if (git_diff_delta__should_skip(ctxt.opts, patch.delta))+ continue;++ if (!(error = diff_patch_load(&ctxt, &patch))) {++ /* invoke file callback */+ error = diff_delta_file_callback(&ctxt, patch.delta, idx);++ /* generate diffs and invoke hunk and line callbacks */+ if (!error)+ error = diff_patch_generate(&ctxt, &patch);++ diff_patch_unload(&patch);+ }++ if (error < 0)+ break;+ }++ if (error == GIT_EUSER)+ giterr_clear(); /* don't let error message leak */++ return error;+}+++typedef struct {+ git_diff_list *diff;+ git_diff_data_cb print_cb;+ void *payload;+ git_buf *buf;+} diff_print_info;++static char pick_suffix(int mode)+{+ if (S_ISDIR(mode))+ return '/';+ else if (mode & 0100) //-V536+ /* in git, modes are very regular, so we must have 0100755 mode */+ return '*';+ else+ return ' ';+}++char git_diff_status_char(git_delta_t status)+{+ char code;++ switch (status) {+ case GIT_DELTA_ADDED: code = 'A'; break;+ case GIT_DELTA_DELETED: code = 'D'; break;+ case GIT_DELTA_MODIFIED: code = 'M'; break;+ case GIT_DELTA_RENAMED: code = 'R'; break;+ case GIT_DELTA_COPIED: code = 'C'; break;+ case GIT_DELTA_IGNORED: code = 'I'; break;+ case GIT_DELTA_UNTRACKED: code = '?'; break;+ default: code = ' '; break;+ }++ return code;+}++static int print_compact(+ const git_diff_delta *delta, float progress, void *data)+{+ diff_print_info *pi = data;+ char old_suffix, new_suffix, code = git_diff_status_char(delta->status);++ GIT_UNUSED(progress);++ if (code == ' ')+ return 0;++ old_suffix = pick_suffix(delta->old_file.mode);+ new_suffix = pick_suffix(delta->new_file.mode);++ git_buf_clear(pi->buf);++ if (delta->old_file.path != delta->new_file.path &&+ pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0)+ git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,+ delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);+ else if (delta->old_file.mode != delta->new_file.mode &&+ delta->old_file.mode != 0 && delta->new_file.mode != 0)+ git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code,+ delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode);+ else if (old_suffix != ' ')+ git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);+ else+ git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path);++ if (git_buf_oom(pi->buf))+ return -1;++ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))+ {+ giterr_clear();+ return GIT_EUSER;+ }++ return 0;+}++int git_diff_print_compact(+ git_diff_list *diff,+ git_diff_data_cb print_cb,+ void *payload)+{+ int error;+ git_buf buf = GIT_BUF_INIT;+ diff_print_info pi;++ pi.diff = diff;+ pi.print_cb = print_cb;+ pi.payload = payload;+ pi.buf = &buf;++ error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi);++ git_buf_free(&buf);++ return error;+}++static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta)+{+ char start_oid[8], end_oid[8];++ /* TODO: Determine a good actual OID range to print */+ git_oid_tostr(start_oid, sizeof(start_oid), &delta->old_file.oid);+ git_oid_tostr(end_oid, sizeof(end_oid), &delta->new_file.oid);++ /* TODO: Match git diff more closely */+ if (delta->old_file.mode == delta->new_file.mode) {+ git_buf_printf(pi->buf, "index %s..%s %o\n",+ start_oid, end_oid, delta->old_file.mode);+ } else {+ if (delta->old_file.mode == 0) {+ git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode);+ } else if (delta->new_file.mode == 0) {+ git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode);+ } else {+ git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode);+ git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode);+ }+ git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid);+ }++ if (git_buf_oom(pi->buf))+ return -1;++ return 0;+}++static int print_patch_file(+ const git_diff_delta *delta, float progress, void *data)+{+ diff_print_info *pi = data;+ const char *oldpfx = pi->diff->opts.old_prefix;+ const char *oldpath = delta->old_file.path;+ const char *newpfx = pi->diff->opts.new_prefix;+ const char *newpath = delta->new_file.path;++ GIT_UNUSED(progress);++ if (S_ISDIR(delta->new_file.mode) ||+ delta->status == GIT_DELTA_UNMODIFIED ||+ delta->status == GIT_DELTA_IGNORED ||+ (delta->status == GIT_DELTA_UNTRACKED &&+ (pi->diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED_CONTENT) == 0))+ return 0;++ if (!oldpfx)+ oldpfx = DIFF_OLD_PREFIX_DEFAULT;++ if (!newpfx)+ newpfx = DIFF_NEW_PREFIX_DEFAULT;++ git_buf_clear(pi->buf);+ git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path);++ if (print_oid_range(pi, delta) < 0)+ return -1;++ if (git_oid_iszero(&delta->old_file.oid)) {+ oldpfx = "";+ oldpath = "/dev/null";+ }+ if (git_oid_iszero(&delta->new_file.oid)) {+ newpfx = "";+ newpath = "/dev/null";+ }++ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) {+ git_buf_printf(pi->buf, "--- %s%s\n", oldpfx, oldpath);+ git_buf_printf(pi->buf, "+++ %s%s\n", newpfx, newpath);+ }++ if (git_buf_oom(pi->buf))+ return -1;++ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR,+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))+ {+ giterr_clear();+ return GIT_EUSER;+ }++ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)+ return 0;++ git_buf_clear(pi->buf);+ git_buf_printf(+ pi->buf, "Binary files %s%s and %s%s differ\n",+ oldpfx, oldpath, newpfx, newpath);+ if (git_buf_oom(pi->buf))+ return -1;++ if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_BINARY,+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))+ {+ giterr_clear();+ return GIT_EUSER;+ }++ return 0;+}++static int print_patch_hunk(+ const git_diff_delta *d,+ const git_diff_range *r,+ const char *header,+ size_t header_len,+ void *data)+{+ diff_print_info *pi = data;++ if (S_ISDIR(d->new_file.mode))+ return 0;++ git_buf_clear(pi->buf);+ if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0)+ return -1;++ if (pi->print_cb(d, r, GIT_DIFF_LINE_HUNK_HDR,+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))+ {+ giterr_clear();+ return GIT_EUSER;+ }++ return 0;+}++static int print_patch_line(+ const git_diff_delta *delta,+ const git_diff_range *range,+ char line_origin, /* GIT_DIFF_LINE value from above */+ const char *content,+ size_t content_len,+ void *data)+{+ diff_print_info *pi = data;++ if (S_ISDIR(delta->new_file.mode))+ return 0;++ git_buf_clear(pi->buf);++ if (line_origin == GIT_DIFF_LINE_ADDITION ||+ line_origin == GIT_DIFF_LINE_DELETION ||+ line_origin == GIT_DIFF_LINE_CONTEXT)+ git_buf_printf(pi->buf, "%c%.*s", line_origin, (int)content_len, content);+ else if (content_len > 0)+ git_buf_printf(pi->buf, "%.*s", (int)content_len, content);++ if (git_buf_oom(pi->buf))+ return -1;++ if (pi->print_cb(delta, range, line_origin,+ git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload))+ {+ giterr_clear();+ return GIT_EUSER;+ }++ return 0;+}++int git_diff_print_patch(+ git_diff_list *diff,+ git_diff_data_cb print_cb,+ void *payload)+{+ int error;+ git_buf buf = GIT_BUF_INIT;+ diff_print_info pi;++ pi.diff = diff;+ pi.print_cb = print_cb;+ pi.payload = payload;+ pi.buf = &buf;++ error = git_diff_foreach(+ diff, print_patch_file, print_patch_hunk, print_patch_line, &pi);++ git_buf_free(&buf);++ return error;+}++static void set_data_from_blob(+ const git_blob *blob, git_map *map, git_diff_file *file)+{+ if (blob) {+ file->size = git_blob_rawsize(blob);+ git_oid_cpy(&file->oid, git_object_id((const git_object *)blob));+ file->mode = 0644;++ map->len = (size_t)file->size;+ map->data = (char *)git_blob_rawcontent(blob);+ } else {+ file->size = 0;+ file->flags |= GIT_DIFF_FLAG__NO_DATA;++ map->len = 0;+ map->data = "";+ }+}++static void set_data_from_buffer(+ const char *buffer, size_t buffer_len, git_map *map, git_diff_file *file)+{+ file->size = (git_off_t)buffer_len;+ file->mode = 0644;+ map->len = buffer_len;++ if (!buffer) {+ file->flags |= GIT_DIFF_FLAG__NO_DATA;+ map->data = NULL;+ } else {+ map->data = (char *)buffer;+ git_odb_hash(&file->oid, buffer, buffer_len, GIT_OBJ_BLOB);+ }+}++typedef struct {+ diff_context ctxt;+ git_diff_delta delta;+ git_diff_patch patch;+} diff_single_data;++static int diff_single_init(+ diff_single_data *data,+ git_repository *repo,+ const git_diff_options *opts,+ git_diff_file_cb file_cb,+ git_diff_hunk_cb hunk_cb,+ git_diff_data_cb data_cb,+ void *payload)+{+ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options");++ memset(data, 0, sizeof(*data));++ if (diff_context_init(+ &data->ctxt, NULL, repo, opts,+ file_cb, hunk_cb, data_cb, payload) < 0)+ return -1;++ diff_patch_init(&data->ctxt, &data->patch);++ return 0;+}++static int diff_single_apply(diff_single_data *data)+{+ int error;+ git_diff_delta *delta = &data->delta;+ bool has_old = ((delta->old_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0);+ bool has_new = ((delta->new_file.flags & GIT_DIFF_FLAG__NO_DATA) == 0);++ /* finish setting up fake git_diff_delta record and loaded data */++ data->patch.delta = delta;+ delta->flags = delta->flags & ~KNOWN_BINARY_FLAGS;++ delta->status = has_new ?+ (has_old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :+ (has_old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);++ if (git_oid_cmp(&delta->new_file.oid, &delta->old_file.oid) == 0)+ delta->status = GIT_DELTA_UNMODIFIED;++ if ((error = diff_delta_is_binary_by_content(+ &data->ctxt, delta, &delta->old_file, &data->patch.old_data)) < 0 ||+ (error = diff_delta_is_binary_by_content(+ &data->ctxt, delta, &delta->new_file, &data->patch.new_data)) < 0)+ goto cleanup;++ data->patch.flags |= GIT_DIFF_PATCH_LOADED;++ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0 &&+ delta->status != GIT_DELTA_UNMODIFIED)+ data->patch.flags |= GIT_DIFF_PATCH_DIFFABLE;++ /* do diffs */++ if (!(error = diff_delta_file_callback(&data->ctxt, delta, 1)))+ error = diff_patch_generate(&data->ctxt, &data->patch);++cleanup:+ if (error == GIT_EUSER)+ giterr_clear();++ diff_patch_unload(&data->patch);++ return error;+}++int git_diff_blobs(+ const git_blob *old_blob,+ const git_blob *new_blob,+ const git_diff_options *options,+ git_diff_file_cb file_cb,+ git_diff_hunk_cb hunk_cb,+ git_diff_data_cb data_cb,+ void *payload)+{+ int error;+ diff_single_data d;+ git_repository *repo =+ new_blob ? git_object_owner((const git_object *)new_blob) :+ old_blob ? git_object_owner((const git_object *)old_blob) : NULL;++ if (!repo) /* Hmm, given two NULL blobs, silently do no callbacks? */+ return 0;++ if ((error = diff_single_init(+ &d, repo, options, file_cb, hunk_cb, data_cb, payload)) < 0)+ return error;++ if (options && (options->flags & GIT_DIFF_REVERSE) != 0) {+ const git_blob *swap = old_blob;+ old_blob = new_blob;+ new_blob = swap;+ }++ set_data_from_blob(old_blob, &d.patch.old_data, &d.delta.old_file);+ set_data_from_blob(new_blob, &d.patch.new_data, &d.delta.new_file);++ return diff_single_apply(&d);+}++int git_diff_blob_to_buffer(+ const git_blob *old_blob,+ const char *buf,+ size_t buflen,+ const git_diff_options *options,+ git_diff_file_cb file_cb,+ git_diff_hunk_cb hunk_cb,+ git_diff_data_cb data_cb,+ void *payload)+{+ int error;+ diff_single_data d;+ git_repository *repo =+ old_blob ? git_object_owner((const git_object *)old_blob) : NULL;++ if (!repo && !buf) /* Hmm, given NULLs, silently do no callbacks? */+ return 0;++ if ((error = diff_single_init(+ &d, repo, options, file_cb, hunk_cb, data_cb, payload)) < 0)+ return error;++ if (options && (options->flags & GIT_DIFF_REVERSE) != 0) {+ set_data_from_buffer(buf, buflen, &d.patch.old_data, &d.delta.old_file);+ set_data_from_blob(old_blob, &d.patch.new_data, &d.delta.new_file);+ } else {+ set_data_from_blob(old_blob, &d.patch.old_data, &d.delta.old_file);+ set_data_from_buffer(buf, buflen, &d.patch.new_data, &d.delta.new_file);+ }++ return diff_single_apply(&d);+}++size_t git_diff_num_deltas(git_diff_list *diff)+{+ assert(diff);+ return (size_t)diff->deltas.length;+}++size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)+{+ size_t i, count = 0;+ git_diff_delta *delta;++ assert(diff);++ git_vector_foreach(&diff->deltas, i, delta) {+ count += (delta->status == type);+ }++ return count;+}++int git_diff_get_patch(+ git_diff_patch **patch_ptr,+ const git_diff_delta **delta_ptr,+ git_diff_list *diff,+ size_t idx)+{+ int error;+ diff_context ctxt;+ git_diff_delta *delta;+ git_diff_patch *patch;++ if (patch_ptr)+ *patch_ptr = NULL;+ if (delta_ptr)+ *delta_ptr = NULL;++ if (diff_required(diff, "git_diff_get_patch") < 0)+ return -1;++ if (diff_context_init(+ &ctxt, diff, NULL, NULL,+ NULL, diff_patch_hunk_cb, diff_patch_line_cb, NULL) < 0)+ return -1;++ delta = git_vector_get(&diff->deltas, idx);+ if (!delta) {+ giterr_set(GITERR_INVALID, "Index out of range for delta in diff");+ return GIT_ENOTFOUND;+ }++ if (delta_ptr)+ *delta_ptr = delta;++ if (!patch_ptr &&+ ((delta->flags & KNOWN_BINARY_FLAGS) != 0 ||+ (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))+ return 0;++ if (git_diff_delta__should_skip(ctxt.opts, delta))+ return 0;++ /* Don't load the patch if the user doesn't want it */+ if (!patch_ptr)+ return 0;++ patch = diff_patch_alloc(&ctxt, delta);+ if (!patch)+ return -1;++ if (!(error = diff_patch_load(&ctxt, patch))) {+ ctxt.payload = patch;++ error = diff_patch_generate(&ctxt, patch);++ if (error == GIT_EUSER)+ error = ctxt.error;+ }++ if (error)+ git_diff_patch_free(patch);+ else if (patch_ptr)+ *patch_ptr = patch;++ return error;+}++void git_diff_patch_free(git_diff_patch *patch)+{+ if (patch)+ GIT_REFCOUNT_DEC(patch, diff_patch_free);+}++const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch)+{+ assert(patch);+ return patch->delta;+}++size_t git_diff_patch_num_hunks(git_diff_patch *patch)+{+ assert(patch);+ return patch->hunks_size;+}++int git_diff_patch_line_stats(+ size_t *total_ctxt,+ size_t *total_adds,+ size_t *total_dels,+ const git_diff_patch *patch)+{+ size_t totals[3], idx;++ memset(totals, 0, sizeof(totals));++ for (idx = 0; idx < patch->lines_size; ++idx) {+ switch (patch->lines[idx].origin) {+ case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;+ case GIT_DIFF_LINE_ADDITION: totals[1]++; break;+ case GIT_DIFF_LINE_DELETION: totals[2]++; break;+ default:+ /* diff --stat and --numstat don't count EOFNL marks because+ * they will always be paired with a ADDITION or DELETION line.+ */+ break;+ }+ }++ if (total_ctxt)+ *total_ctxt = totals[0];+ if (total_adds)+ *total_adds = totals[1];+ if (total_dels)+ *total_dels = totals[2];++ return 0;+}++int git_diff_patch_get_hunk(+ const git_diff_range **range,+ const char **header,+ size_t *header_len,+ size_t *lines_in_hunk,+ git_diff_patch *patch,+ size_t hunk_idx)+{+ diff_patch_hunk *hunk;++ assert(patch);++ if (hunk_idx >= patch->hunks_size) {+ if (range) *range = NULL;+ if (header) *header = NULL;+ if (header_len) *header_len = 0;+ if (lines_in_hunk) *lines_in_hunk = 0;+ return GIT_ENOTFOUND;+ }++ hunk = &patch->hunks[hunk_idx];++ if (range) *range = &hunk->range;+ if (header) *header = hunk->header;+ if (header_len) *header_len = hunk->header_len;+ if (lines_in_hunk) *lines_in_hunk = hunk->line_count;++ return 0;+}++int git_diff_patch_num_lines_in_hunk(+ git_diff_patch *patch,+ size_t hunk_idx)+{+ assert(patch);++ if (hunk_idx >= patch->hunks_size)+ return GIT_ENOTFOUND;+ else+ return (int)patch->hunks[hunk_idx].line_count;+}++int git_diff_patch_get_line_in_hunk(+ char *line_origin,+ const char **content,+ size_t *content_len,+ int *old_lineno,+ int *new_lineno,+ git_diff_patch *patch,+ size_t hunk_idx,+ size_t line_of_hunk)+{+ diff_patch_hunk *hunk;+ diff_patch_line *line;++ assert(patch);++ if (hunk_idx >= patch->hunks_size)+ goto notfound;+ hunk = &patch->hunks[hunk_idx];++ if (line_of_hunk >= hunk->line_count)+ goto notfound;++ line = &patch->lines[hunk->line_start + line_of_hunk];++ if (line_origin) *line_origin = line->origin;+ if (content) *content = line->ptr;+ if (content_len) *content_len = line->len;+ if (old_lineno) *old_lineno = (int)line->oldno;+ if (new_lineno) *new_lineno = (int)line->newno;++ return 0;++notfound:+ if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT;+ if (content) *content = NULL;+ if (content_len) *content_len = 0;+ if (old_lineno) *old_lineno = -1;+ if (new_lineno) *new_lineno = -1;++ return GIT_ENOTFOUND;+}++static int print_to_buffer_cb(+ const git_diff_delta *delta,+ const git_diff_range *range,+ char line_origin,+ const char *content,+ size_t content_len,+ void *payload)+{+ git_buf *output = payload;+ GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);+ return git_buf_put(output, content, content_len);+}++int git_diff_patch_print(+ git_diff_patch *patch,+ git_diff_data_cb print_cb,+ void *payload)+{+ int error;+ git_buf temp = GIT_BUF_INIT;+ diff_print_info pi;+ size_t h, l;++ assert(patch && print_cb);++ pi.diff = patch->diff;+ pi.print_cb = print_cb;+ pi.payload = payload;+ pi.buf = &temp;++ error = print_patch_file(patch->delta, 0, &pi);++ for (h = 0; h < patch->hunks_size && !error; ++h) {+ diff_patch_hunk *hunk = &patch->hunks[h];++ error = print_patch_hunk(+ patch->delta, &hunk->range, hunk->header, hunk->header_len, &pi);++ for (l = 0; l < hunk->line_count && !error; ++l) {+ diff_patch_line *line = &patch->lines[hunk->line_start + l];++ error = print_patch_line(+ patch->delta, &hunk->range,+ line->origin, line->ptr, line->len, &pi);+ }+ }++ git_buf_free(&temp);++ return error;+}++int git_diff_patch_to_str(+ char **string,+ git_diff_patch *patch)+{+ int error;+ git_buf output = GIT_BUF_INIT;++ error = git_diff_patch_print(patch, print_to_buffer_cb, &output);++ /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,+ * meaning a memory allocation failure, so just map to -1...+ */+ if (error == GIT_EUSER)+ error = -1;++ *string = git_buf_detach(&output);++ return error;+}++int git_diff__paired_foreach(+ git_diff_list *idx2head,+ git_diff_list *wd2idx,+ int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),+ void *payload)+{+ int cmp;+ git_diff_delta *i2h, *w2i;+ size_t i, j, i_max, j_max;+ int (*strcomp)(const char *, const char *);++ i_max = idx2head ? idx2head->deltas.length : 0;+ j_max = wd2idx ? wd2idx->deltas.length : 0;++ /* Get appropriate strcmp function */+ strcomp = idx2head ? idx2head->strcomp : wd2idx ? wd2idx->strcomp : NULL;++ /* Assert both iterators use matching ignore-case. If this function ever+ * supports merging diffs that are not sorted by the same function, then+ * it will need to spool and sort on one of the results before merging+ */+ if (idx2head && wd2idx) {+ assert(idx2head->strcomp == wd2idx->strcomp);+ }++ for (i = 0, j = 0; i < i_max || j < j_max; ) {+ i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;+ w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;++ cmp = !w2i ? -1 : !i2h ? 1 :+ strcomp(i2h->old_file.path, w2i->old_file.path);++ if (cmp < 0) {+ if (cb(i2h, NULL, payload))+ return GIT_EUSER;+ i++;+ } else if (cmp > 0) {+ if (cb(NULL, w2i, payload))+ return GIT_EUSER;+ j++;+ } else {+ if (cb(i2h, w2i, payload))+ return GIT_EUSER;+ i++; j++;+ }+ } return 0; }
+ libgit2/src/diff_output.h view
@@ -0,0 +1,93 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_diff_output_h__+#define INCLUDE_diff_output_h__++#include "git2/blob.h"+#include "diff.h"+#include "map.h"+#include "xdiff/xdiff.h"++#define MAX_DIFF_FILESIZE 0x20000000++enum {+ GIT_DIFF_PATCH_ALLOCATED = (1 << 0),+ GIT_DIFF_PATCH_PREPPED = (1 << 1),+ GIT_DIFF_PATCH_LOADED = (1 << 2),+ GIT_DIFF_PATCH_DIFFABLE = (1 << 3),+ GIT_DIFF_PATCH_DIFFED = (1 << 4),+};++/* context for performing diffs */+typedef struct {+ git_repository *repo;+ git_diff_list *diff;+ const git_diff_options *opts;+ git_diff_file_cb file_cb;+ git_diff_hunk_cb hunk_cb;+ git_diff_data_cb data_cb;+ void *payload;+ int error;+ git_diff_range range;+ xdemitconf_t xdiff_config;+ xpparam_t xdiff_params;+} diff_context;++/* cached information about a single span in a diff */+typedef struct diff_patch_line diff_patch_line;+struct diff_patch_line {+ const char *ptr;+ size_t len;+ size_t lines, oldno, newno;+ char origin;+};++/* cached information about a hunk in a diff */+typedef struct diff_patch_hunk diff_patch_hunk;+struct diff_patch_hunk {+ git_diff_range range;+ char header[128];+ size_t header_len;+ size_t line_start;+ size_t line_count;+};++struct git_diff_patch {+ git_refcount rc;+ git_diff_list *diff; /* for refcount purposes, maybe NULL for blob diffs */+ git_diff_delta *delta;+ diff_context *ctxt; /* only valid while generating patch */+ git_iterator_type_t old_src;+ git_iterator_type_t new_src;+ git_blob *old_blob;+ git_blob *new_blob;+ git_map old_data;+ git_map new_data;+ uint32_t flags;+ diff_patch_hunk *hunks;+ size_t hunks_asize, hunks_size;+ diff_patch_line *lines;+ size_t lines_asize, lines_size;+ size_t oldno, newno;+};++/* context for performing diff on a single delta */+typedef struct {+ git_diff_patch *patch;+ uint32_t prepped : 1;+ uint32_t loaded : 1;+ uint32_t diffable : 1;+ uint32_t diffed : 1;+} diff_delta_context;++extern int git_diff__paired_foreach(+ git_diff_list *idx2head,+ git_diff_list *wd2idx,+ int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),+ void *payload);++#endif
+ libgit2/src/diff_tform.c view
@@ -0,0 +1,687 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#include "common.h"+#include "diff.h"+#include "git2/config.h"+#include "git2/blob.h"+#include "hashsig.h"++static git_diff_delta *diff_delta__dup(+ const git_diff_delta *d, git_pool *pool)+{+ git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));+ if (!delta)+ return NULL;++ memcpy(delta, d, sizeof(git_diff_delta));++ delta->old_file.path = git_pool_strdup(pool, d->old_file.path);+ if (delta->old_file.path == NULL)+ goto fail;++ if (d->new_file.path != d->old_file.path) {+ delta->new_file.path = git_pool_strdup(pool, d->new_file.path);+ if (delta->new_file.path == NULL)+ goto fail;+ } else {+ delta->new_file.path = delta->old_file.path;+ }++ return delta;++fail:+ git__free(delta);+ return NULL;+}++static git_diff_delta *diff_delta__merge_like_cgit(+ const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)+{+ git_diff_delta *dup;++ /* Emulate C git for merging two diffs (a la 'git diff <sha>').+ *+ * When C git does a diff between the work dir and a tree, it actually+ * diffs with the index but uses the workdir contents. This emulates+ * those choices so we can emulate the type of diff.+ *+ * We have three file descriptions here, let's call them:+ * f1 = a->old_file+ * f2 = a->new_file AND b->old_file+ * f3 = b->new_file+ */++ /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */+ if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)+ return diff_delta__dup(a, pool);++ /* otherwise, base this diff on the 'b' diff */+ if ((dup = diff_delta__dup(b, pool)) == NULL)+ return NULL;++ /* If 'a' status is uninteresting, then we're done */+ if (a->status == GIT_DELTA_UNMODIFIED)+ return dup;++ assert(a->status != GIT_DELTA_UNMODIFIED);+ assert(b->status != GIT_DELTA_UNMODIFIED);++ /* A cgit exception is that the diff of a file that is only in the+ * index (i.e. not in HEAD nor workdir) is given as empty.+ */+ if (dup->status == GIT_DELTA_DELETED) {+ if (a->status == GIT_DELTA_ADDED)+ dup->status = GIT_DELTA_UNMODIFIED;+ /* else don't overwrite DELETE status */+ } else {+ dup->status = a->status;+ }++ git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);+ dup->old_file.mode = a->old_file.mode;+ dup->old_file.size = a->old_file.size;+ dup->old_file.flags = a->old_file.flags;++ return dup;+}++int git_diff_merge(+ git_diff_list *onto,+ const git_diff_list *from)+{+ int error = 0;+ git_pool onto_pool;+ git_vector onto_new;+ git_diff_delta *delta;+ bool ignore_case = false;+ unsigned int i, j;++ assert(onto && from);++ if (!from->deltas.length)+ return 0;++ if (git_vector_init(+ &onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 ||+ git_pool_init(&onto_pool, 1, 0) < 0)+ return -1;++ if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ||+ (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)+ {+ ignore_case = true;++ /* This function currently only supports merging diff lists that+ * are sorted identically. */+ assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&+ (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0);+ }++ for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {+ git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);+ const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);+ int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);++ if (cmp < 0) {+ delta = diff_delta__dup(o, &onto_pool);+ i++;+ } else if (cmp > 0) {+ delta = diff_delta__dup(f, &onto_pool);+ j++;+ } else {+ delta = diff_delta__merge_like_cgit(o, f, &onto_pool);+ i++;+ j++;+ }++ /* the ignore rules for the target may not match the source+ * or the result of a merged delta could be skippable...+ */+ if (git_diff_delta__should_skip(&onto->opts, delta)) {+ git__free(delta);+ continue;+ }++ if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)+ break;+ }++ if (!error) {+ git_vector_swap(&onto->deltas, &onto_new);+ git_pool_swap(&onto->pool, &onto_pool);+ onto->new_src = from->new_src;++ /* prefix strings also come from old pool, so recreate those.*/+ onto->opts.old_prefix =+ git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix);+ onto->opts.new_prefix =+ git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix);+ }++ git_vector_foreach(&onto_new, i, delta)+ git__free(delta);+ git_vector_free(&onto_new);+ git_pool_clear(&onto_pool);++ return error;+}++static int find_similar__hashsig_for_file(+ void **out, const git_diff_file *f, const char *path, void *p)+{+ git_hashsig_option_t opt = (git_hashsig_option_t)p;+ int error = 0;++ GIT_UNUSED(f);+ error = git_hashsig_create_fromfile((git_hashsig **)out, path, opt);+ + if (error == GIT_EBUFS) {+ error = 0;+ giterr_clear();+ }++ return error;+}++static int find_similar__hashsig_for_buf(+ void **out, const git_diff_file *f, const char *buf, size_t len, void *p)+{+ git_hashsig_option_t opt = (git_hashsig_option_t)p;+ int error = 0;+ + GIT_UNUSED(f);+ error = git_hashsig_create((git_hashsig **)out, buf, len, opt);+ + if (error == GIT_EBUFS) {+ error = 0;+ giterr_clear();+ }++ return error;+}++static void find_similar__hashsig_free(void *sig, void *payload)+{+ GIT_UNUSED(payload);+ git_hashsig_free(sig);+}++static int find_similar__calc_similarity(+ int *score, void *siga, void *sigb, void *payload)+{+ GIT_UNUSED(payload);+ *score = git_hashsig_compare(siga, sigb);+ return 0;+}++#define DEFAULT_THRESHOLD 50+#define DEFAULT_BREAK_REWRITE_THRESHOLD 60+#define DEFAULT_TARGET_LIMIT 200++static int normalize_find_opts(+ git_diff_list *diff,+ git_diff_find_options *opts,+ git_diff_find_options *given)+{+ git_config *cfg = NULL;++ if (diff->repo != NULL &&+ git_repository_config__weakptr(&cfg, diff->repo) < 0)+ return -1;++ if (given != NULL)+ memcpy(opts, given, sizeof(*opts));+ else {+ const char *val = NULL;++ GIT_INIT_STRUCTURE(opts, GIT_DIFF_FIND_OPTIONS_VERSION);++ opts->flags = GIT_DIFF_FIND_RENAMES;++ if (git_config_get_string(&val, cfg, "diff.renames") < 0)+ giterr_clear();+ else if (val &&+ (!strcasecmp(val, "copies") || !strcasecmp(val, "copy")))+ opts->flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;+ }++ GITERR_CHECK_VERSION(opts, GIT_DIFF_FIND_OPTIONS_VERSION, "git_diff_find_options");++ /* some flags imply others */++ if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES)+ opts->flags |= GIT_DIFF_FIND_RENAMES;++ if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)+ opts->flags |= GIT_DIFF_FIND_COPIES;++#define USE_DEFAULT(X) ((X) == 0 || (X) > 100)++ if (USE_DEFAULT(opts->rename_threshold))+ opts->rename_threshold = DEFAULT_THRESHOLD;++ if (USE_DEFAULT(opts->rename_from_rewrite_threshold))+ opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD;++ if (USE_DEFAULT(opts->copy_threshold))+ opts->copy_threshold = DEFAULT_THRESHOLD;++ if (USE_DEFAULT(opts->break_rewrite_threshold))+ opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD;++#undef USE_DEFAULT++ if (!opts->target_limit) {+ int32_t limit = 0;++ opts->target_limit = DEFAULT_TARGET_LIMIT;++ if (git_config_get_int32(&limit, cfg, "diff.renameLimit") < 0)+ giterr_clear();+ else if (limit > 0)+ opts->target_limit = limit;+ }++ /* assign the internal metric with whitespace flag as payload */+ if (!opts->metric) {+ opts->metric = git__malloc(sizeof(git_diff_similarity_metric));+ GITERR_CHECK_ALLOC(opts->metric);++ opts->metric->file_signature = find_similar__hashsig_for_file;+ opts->metric->buffer_signature = find_similar__hashsig_for_buf;+ opts->metric->free_signature = find_similar__hashsig_free;+ opts->metric->similarity = find_similar__calc_similarity;++ if (opts->flags & GIT_DIFF_FIND_IGNORE_WHITESPACE)+ opts->metric->payload = (void *)GIT_HASHSIG_IGNORE_WHITESPACE;+ else if (opts->flags & GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE)+ opts->metric->payload = (void *)GIT_HASHSIG_NORMAL;+ else+ opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE;+ }++ return 0;+}++static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)+{+ git_vector onto = GIT_VECTOR_INIT;+ size_t i;+ git_diff_delta *delta;++ if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0)+ return -1;++ /* build new delta list without TO_DELETE and splitting TO_SPLIT */+ git_vector_foreach(&diff->deltas, i, delta) {+ if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)+ continue;++ if ((delta->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0) {+ git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);+ if (!deleted)+ goto on_error;++ deleted->status = GIT_DELTA_DELETED;+ memset(&deleted->new_file, 0, sizeof(deleted->new_file));+ deleted->new_file.path = deleted->old_file.path;+ deleted->new_file.flags |= GIT_DIFF_FLAG_VALID_OID;++ if (git_vector_insert(&onto, deleted) < 0)+ goto on_error;++ delta->status = GIT_DELTA_ADDED;+ memset(&delta->old_file, 0, sizeof(delta->old_file));+ delta->old_file.path = delta->new_file.path;+ delta->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;+ }++ if (git_vector_insert(&onto, delta) < 0)+ goto on_error;+ }++ /* cannot return an error past this point */+ git_vector_foreach(&diff->deltas, i, delta)+ if ((delta->flags & GIT_DIFF_FLAG__TO_DELETE) != 0)+ git__free(delta);++ /* swap new delta list into place */+ git_vector_sort(&onto);+ git_vector_swap(&diff->deltas, &onto);+ git_vector_free(&onto);++ return 0;++on_error:+ git_vector_foreach(&onto, i, delta)+ git__free(delta);++ git_vector_free(&onto);++ return -1;+}++GIT_INLINE(git_diff_file *) similarity_get_file(git_diff_list *diff, size_t idx)+{+ git_diff_delta *delta = git_vector_get(&diff->deltas, idx / 2);+ return (idx & 1) ? &delta->new_file : &delta->old_file;+}++static int similarity_calc(+ git_diff_list *diff,+ git_diff_find_options *opts,+ size_t file_idx,+ void **cache)+{+ int error = 0;+ git_diff_file *file = similarity_get_file(diff, file_idx);+ git_iterator_type_t src = (file_idx & 1) ? diff->old_src : diff->new_src;++ if (src == GIT_ITERATOR_TYPE_WORKDIR) { /* compute hashsig from file */+ git_buf path = GIT_BUF_INIT;++ /* TODO: apply wd-to-odb filters to file data if necessary */++ if (!(error = git_buf_joinpath(+ &path, git_repository_workdir(diff->repo), file->path)))+ error = opts->metric->file_signature(+ &cache[file_idx], file, path.ptr, opts->metric->payload);++ git_buf_free(&path);+ } else { /* compute hashsig from blob buffer */+ git_blob *blob = NULL;+ git_off_t blobsize;++ /* TODO: add max size threshold a la diff? */++ if ((error = git_blob_lookup(&blob, diff->repo, &file->oid)) < 0)+ return error;++ blobsize = git_blob_rawsize(blob);+ if (!git__is_sizet(blobsize)) /* ? what to do ? */+ blobsize = (size_t)-1;++ error = opts->metric->buffer_signature(+ &cache[file_idx], file, git_blob_rawcontent(blob),+ (size_t)blobsize, opts->metric->payload);++ git_blob_free(blob);+ }++ return error;+}++static int similarity_measure(+ git_diff_list *diff,+ git_diff_find_options *opts,+ void **cache,+ size_t a_idx,+ size_t b_idx)+{+ int score = 0;+ git_diff_file *a_file = similarity_get_file(diff, a_idx);+ git_diff_file *b_file = similarity_get_file(diff, b_idx);++ if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode))+ return 0;++ if (git_oid_cmp(&a_file->oid, &b_file->oid) == 0)+ return 100;++ /* update signature cache if needed */+ if (!cache[a_idx] && similarity_calc(diff, opts, a_idx, cache) < 0)+ return -1;+ if (!cache[b_idx] && similarity_calc(diff, opts, b_idx, cache) < 0)+ return -1;+ + /* some metrics may not wish to process this file (too big / too small) */+ if (!cache[a_idx] || !cache[b_idx])+ return 0;++ /* compare signatures */+ if (opts->metric->similarity(+ &score, cache[a_idx], cache[b_idx], opts->metric->payload) < 0)+ return -1;++ /* clip score */+ if (score < 0)+ score = 0;+ else if (score > 100)+ score = 100;++ return score;+}++#define FLAG_SET(opts,flag_name) ((opts.flags & flag_name) != 0)++int git_diff_find_similar(+ git_diff_list *diff,+ git_diff_find_options *given_opts)+{+ size_t i, j, cache_size, *matches;+ int error = 0, similarity;+ git_diff_delta *from, *to;+ git_diff_find_options opts;+ size_t tried_targets, num_rewrites = 0;+ void **cache;++ if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)+ return error;++ /* TODO: maybe abort if deltas.length > target_limit ??? */++ cache_size = diff->deltas.length * 2; /* must store b/c length may change */+ cache = git__calloc(cache_size, sizeof(void *));+ GITERR_CHECK_ALLOC(cache);++ matches = git__calloc(diff->deltas.length, sizeof(size_t));+ GITERR_CHECK_ALLOC(matches);++ /* first break MODIFIED records that are too different (if requested) */++ if (FLAG_SET(opts, GIT_DIFF_FIND_AND_BREAK_REWRITES)) {+ git_vector_foreach(&diff->deltas, i, from) {+ if (from->status != GIT_DELTA_MODIFIED)+ continue;++ similarity = similarity_measure(+ diff, &opts, cache, 2 * i, 2 * i + 1);++ if (similarity < 0) {+ error = similarity;+ goto cleanup;+ }++ if ((unsigned int)similarity < opts.break_rewrite_threshold) {+ from->flags |= GIT_DIFF_FLAG__TO_SPLIT;+ num_rewrites++;+ }+ }+ }++ /* next find the most similar delta for each rename / copy candidate */++ git_vector_foreach(&diff->deltas, i, from) {+ tried_targets = 0;++ /* skip things that aren't blobs */+ if (GIT_MODE_TYPE(from->old_file.mode) !=+ GIT_MODE_TYPE(GIT_FILEMODE_BLOB))+ continue;++ /* don't check UNMODIFIED files as source unless given option */+ if (from->status == GIT_DELTA_UNMODIFIED &&+ !FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))+ continue;++ /* skip all but DELETED files unless copy detection is on */+ if (!FLAG_SET(opts, GIT_DIFF_FIND_COPIES) &&+ from->status != GIT_DELTA_DELETED &&+ (from->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0)+ continue;++ git_vector_foreach(&diff->deltas, j, to) {+ if (i == j)+ continue;++ /* skip things that aren't blobs */+ if (GIT_MODE_TYPE(to->new_file.mode) !=+ GIT_MODE_TYPE(GIT_FILEMODE_BLOB))+ continue;++ switch (to->status) {+ case GIT_DELTA_ADDED:+ case GIT_DELTA_UNTRACKED:+ case GIT_DELTA_RENAMED:+ case GIT_DELTA_COPIED:+ break;+ case GIT_DELTA_MODIFIED:+ if ((to->flags & GIT_DIFF_FLAG__TO_SPLIT) == 0)+ continue;+ break;+ default:+ /* only the above status values should be checked */+ continue;+ }++ /* cap on maximum files we'll examine (per "from" file) */+ if (++tried_targets > opts.target_limit)+ break;++ /* calculate similarity and see if this pair beats the+ * similarity score of the current best pair.+ */+ similarity = similarity_measure(+ diff, &opts, cache, 2 * i, 2 * j + 1);++ if (similarity < 0) {+ error = similarity;+ goto cleanup;+ }++ if (to->similarity < (unsigned int)similarity) {+ to->similarity = (unsigned int)similarity;+ matches[j] = i + 1;+ }+ }+ }++ /* next rewrite the diffs with renames / copies */++ git_vector_foreach(&diff->deltas, j, to) {+ if (!matches[j]) {+ assert(to->similarity == 0);+ continue;+ }++ i = matches[j] - 1;+ from = GIT_VECTOR_GET(&diff->deltas, i);+ assert(from);++ /* four possible outcomes here:+ * 1. old DELETED and if over rename threshold,+ * new becomes RENAMED and old goes away+ * 2. old SPLIT and if over rename threshold,+ * new becomes RENAMED and old becomes ADDED (clear SPLIT)+ * 3. old was MODIFIED but FIND_RENAMES_FROM_REWRITES is on and+ * old is more similar to new than it is to itself, in which+ * case, new becomes RENAMED and old becomed ADDED+ * 4. otherwise if over copy threshold, new becomes COPIED+ */++ if (from->status == GIT_DELTA_DELETED) {+ if (to->similarity < opts.rename_threshold) {+ to->similarity = 0;+ continue;+ }++ to->status = GIT_DELTA_RENAMED;+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));++ from->flags |= GIT_DIFF_FLAG__TO_DELETE;+ num_rewrites++;++ continue;+ }++ if (from->status == GIT_DELTA_MODIFIED &&+ (from->flags & GIT_DIFF_FLAG__TO_SPLIT) != 0)+ {+ if (to->similarity < opts.rename_threshold) {+ to->similarity = 0;+ continue;+ }++ to->status = GIT_DELTA_RENAMED;+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));++ from->status = GIT_DELTA_ADDED;+ from->flags &= ~GIT_DIFF_FLAG__TO_SPLIT;+ memset(&from->old_file, 0, sizeof(from->old_file));+ num_rewrites--;++ continue;+ }++ if (from->status == GIT_DELTA_MODIFIED &&+ FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&+ to->similarity > opts.rename_threshold)+ {+ similarity = similarity_measure(+ diff, &opts, cache, 2 * i, 2 * i + 1);++ if (similarity < 0) {+ error = similarity;+ goto cleanup;+ }++ if ((unsigned int)similarity < opts.rename_from_rewrite_threshold) {+ to->status = GIT_DELTA_RENAMED;+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));++ from->status = GIT_DELTA_ADDED;+ memset(&from->old_file, 0, sizeof(from->old_file));+ from->old_file.path = to->old_file.path;+ from->old_file.flags |= GIT_DIFF_FLAG_VALID_OID;++ continue;+ }+ }++ if (to->similarity < opts.copy_threshold) {+ to->similarity = 0;+ continue;+ }++ /* convert "to" to a COPIED record */+ to->status = GIT_DELTA_COPIED;+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));+ }++ if (num_rewrites > 0) {+ assert(num_rewrites < diff->deltas.length);++ error = apply_splits_and_deletes(+ diff, diff->deltas.length - num_rewrites);+ }++cleanup:+ git__free(matches);++ for (i = 0; i < cache_size; ++i) {+ if (cache[i] != NULL)+ opts.metric->free_signature(cache[i], opts.metric->payload);+ }+ git__free(cache);++ if (!given_opts || !given_opts->metric)+ git__free(opts.metric);++ return error;+}++#undef FLAG_SET
libgit2/src/errors.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -40,52 +40,34 @@ { git_buf buf = GIT_BUF_INIT; va_list arglist;-- int unix_error_code = 0;- #ifdef GIT_WIN32- DWORD win32_error_code = 0;-#endif-- if (error_class == GITERR_OS) {- unix_error_code = errno;- errno = 0;--#ifdef GIT_WIN32- win32_error_code = GetLastError();- SetLastError(0);+ DWORD win32_error_code = (error_class == GITERR_OS) ? GetLastError() : 0; #endif- }+ int error_code = (error_class == GITERR_OS) ? errno : 0; va_start(arglist, string); git_buf_vprintf(&buf, string, arglist); va_end(arglist); - /* automatically suffix strerror(errno) for GITERR_OS errors */ if (error_class == GITERR_OS) {-- if (unix_error_code != 0) {- git_buf_PUTS(&buf, ": ");- git_buf_puts(&buf, strerror(unix_error_code));- }- #ifdef GIT_WIN32- else if (win32_error_code != 0) {- LPVOID lpMsgBuf = NULL;-- FormatMessage(- FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM |- FORMAT_MESSAGE_IGNORE_INSERTS,- NULL, win32_error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);+ char * win32_error = git_win32_get_error_message(win32_error_code);+ if (win32_error) {+ git_buf_PUTS(&buf, ": ");+ git_buf_puts(&buf, win32_error);+ git__free(win32_error); - if (lpMsgBuf) {- git_buf_PUTS(&buf, ": ");- git_buf_puts(&buf, lpMsgBuf);- LocalFree(lpMsgBuf);- }+ SetLastError(0); }+ else #endif+ if (error_code) {+ git_buf_PUTS(&buf, ": ");+ git_buf_puts(&buf, strerror(error_code));+ }++ if (error_code)+ errno = 0; } if (!git_buf_oom(&buf))@@ -94,22 +76,40 @@ void giterr_set_str(int error_class, const char *string) {- char *message = git__strdup(string);+ char *message; + assert(string);++ message = git__strdup(string);+ if (message) set_error(error_class, message); } -void giterr_set_regex(const regex_t *regex, int error_code)+int giterr_set_regex(const regex_t *regex, int error_code) { char error_buf[1024];++ assert(error_code);+ regerror(error_code, regex, error_buf, sizeof(error_buf)); giterr_set_str(GITERR_REGEX, error_buf);++ if (error_code == REG_NOMATCH)+ return GIT_ENOTFOUND;++ return GIT_EINVALIDSPEC; } void giterr_clear(void) {+ set_error(0, NULL); GIT_GLOBAL->last_error = NULL;++ errno = 0;+#ifdef GIT_WIN32+ SetLastError(0);+#endif } const git_error *giterr_last(void)
libgit2/src/fetch.c view
@@ -1,18 +1,16 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#include "git2/remote.h" #include "git2/oid.h" #include "git2/refs.h" #include "git2/revwalk.h"-#include "git2/indexer.h"+#include "git2/transport.h" #include "common.h"-#include "transport.h" #include "remote.h" #include "refspec.h" #include "pack.h"@@ -21,7 +19,7 @@ struct filter_payload { git_remote *remote;- const git_refspec *spec;+ const git_refspec *spec, *tagspec; git_odb *odb; int found_head; };@@ -29,18 +27,21 @@ static int filter_ref__cb(git_remote_head *head, void *payload) { struct filter_payload *p = payload;+ int match = 0; - if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) {+ if (!git_reference_is_valid_name(head->name))+ return 0;++ if (!p->found_head && strcmp(head->name, GIT_HEAD_FILE) == 0) p->found_head = 1;- } else {- /* If it doesn't match the refpec, we don't want it */- if (!git_refspec_src_matches(p->spec, head->name))- return 0;+ else if (git_refspec_src_matches(p->spec, head->name))+ match = 1;+ else if (p->remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL &&+ git_refspec_src_matches(p->tagspec, head->name))+ match = 1; - /* Don't even try to ask for the annotation target */- if (!git__suffixcmp(head->name, "^{}"))- return 0;- }+ if (!match)+ return 0; /* If we have the object, mark it so we don't ask for it */ if (git_odb_exists(p->odb, &head->oid))@@ -54,8 +55,12 @@ static int filter_wants(git_remote *remote) { struct filter_payload p;+ git_refspec tagspec;+ int error = -1; git_vector_clear(&remote->refs);+ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)+ return error; /* * The fetch refspec can be NULL, and what this means is that the@@ -64,13 +69,19 @@ * HEAD, which will be stored in FETCH_HEAD after the fetch. */ p.spec = git_remote_fetchspec(remote);+ p.tagspec = &tagspec; p.found_head = 0; p.remote = remote; if (git_repository_odb__weakptr(&p.odb, remote->repo) < 0)- return -1;+ goto cleanup; - return remote->transport->ls(remote->transport, &filter_ref__cb, &p);+ error = git_remote_ls(remote, filter_ref__cb, &p);++cleanup:+ git_refspec__free(&tagspec);++ return error; } /*@@ -81,7 +92,7 @@ int git_fetch_negotiate(git_remote *remote) { git_transport *t = remote->transport;-+ if (filter_wants(remote) < 0) { giterr_set(GITERR_NET, "Failed to filter the reference list for wants"); return -1;@@ -92,109 +103,24 @@ return 0; /*- * Now we have everything set up so we can start tell the server- * what we want and what we have.+ * Now we have everything set up so we can start tell the+ * server what we want and what we have. */- return t->negotiate_fetch(t, remote->repo, &remote->refs);+ return t->negotiate_fetch(t,+ remote->repo,+ (const git_remote_head * const *)remote->refs.contents,+ remote->refs.length); } -int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats)+int git_fetch_download_pack(+ git_remote *remote,+ git_transfer_progress_callback progress_cb,+ void *progress_payload) {+ git_transport *t = remote->transport;+ if(!remote->need_pack) return 0; - return remote->transport->download_pack(remote->transport, remote->repo, bytes, stats);-}--/* Receiving data from a socket and storing it is pretty much the same for git and HTTP */-int git_fetch__download_pack(- const char *buffered,- size_t buffered_size,- GIT_SOCKET fd,- git_repository *repo,- git_off_t *bytes,- git_indexer_stats *stats)-{- int recvd;- char buff[1024];- gitno_buffer buf;- git_indexer_stream *idx;-- gitno_buffer_setup(&buf, buff, sizeof(buff), fd);-- if (memcmp(buffered, "PACK", strlen("PACK"))) {- giterr_set(GITERR_NET, "The pack doesn't start with the signature");- return -1;- }-- if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0)- return -1;-- memset(stats, 0, sizeof(git_indexer_stats));- if (git_indexer_stream_add(idx, buffered, buffered_size, stats) < 0)- goto on_error;-- *bytes = buffered_size;-- do {- if (git_indexer_stream_add(idx, buf.data, buf.offset, stats) < 0)- goto on_error;-- gitno_consume_n(&buf, buf.offset);- if ((recvd = gitno_recv(&buf)) < 0)- goto on_error;-- *bytes += recvd;- } while(recvd > 0);-- if (git_indexer_stream_finalize(idx, stats))- goto on_error;-- git_indexer_stream_free(idx);- return 0;--on_error:- git_indexer_stream_free(idx);- return -1;-}--int git_fetch_setup_walk(git_revwalk **out, git_repository *repo)-{- git_revwalk *walk;- git_strarray refs;- unsigned int i;- git_reference *ref;-- if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)- return -1;-- if (git_revwalk_new(&walk, repo) < 0)- return -1;-- git_revwalk_sorting(walk, GIT_SORT_TIME);-- for (i = 0; i < refs.count; ++i) {- /* No tags */- if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))- continue;-- if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0)- goto on_error;-- if (git_reference_type(ref) == GIT_REF_SYMBOLIC)- continue;- if (git_revwalk_push(walk, git_reference_oid(ref)) < 0)- goto on_error;-- git_reference_free(ref);- }-- git_strarray_free(&refs);- *out = walk;- return 0;--on_error:- git_reference_free(ref);- git_strarray_free(&refs);- return -1;+ return t->download_pack(t, remote->repo, &remote->stats, progress_cb, progress_payload); }
libgit2/src/fetch.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,10 +10,19 @@ #include "netops.h" int git_fetch_negotiate(git_remote *remote);-int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); -int git_fetch__download_pack(const char *buffered, size_t buffered_size, GIT_SOCKET fd,- git_repository *repo, git_off_t *bytes, git_indexer_stats *stats);+int git_fetch_download_pack(+ git_remote *remote,+ git_transfer_progress_callback progress_cb,+ void *progress_payload);++int git_fetch__download_pack(+ git_transport *t,+ git_repository *repo,+ git_transfer_progress *stats,+ git_transfer_progress_callback progress_cb,+ void *progress_payload);+ int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); #endif
+ libgit2/src/fetchhead.c view
@@ -0,0 +1,295 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "git2/types.h"+#include "git2/oid.h"++#include "fetchhead.h"+#include "common.h"+#include "buffer.h"+#include "fileops.h"+#include "filebuf.h"+#include "refs.h"+#include "repository.h"++int git_fetchhead_ref_cmp(const void *a, const void *b)+{+ const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;+ const git_fetchhead_ref *two = (const git_fetchhead_ref *)b;++ if (one->is_merge && !two->is_merge)+ return -1;+ if (two->is_merge && !one->is_merge)+ return 1;++ if (one->ref_name && two->ref_name)+ return strcmp(one->ref_name, two->ref_name);+ else if (one->ref_name)+ return -1;+ else if (two->ref_name)+ return 1;++ return 0;+}++int git_fetchhead_ref_create(+ git_fetchhead_ref **out,+ git_oid *oid,+ unsigned int is_merge,+ const char *ref_name,+ const char *remote_url)+{+ git_fetchhead_ref *fetchhead_ref;++ assert(out && oid);++ *out = NULL;++ fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));+ GITERR_CHECK_ALLOC(fetchhead_ref);++ memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));++ git_oid_cpy(&fetchhead_ref->oid, oid);+ fetchhead_ref->is_merge = is_merge;++ if (ref_name)+ fetchhead_ref->ref_name = git__strdup(ref_name);++ if (remote_url)+ fetchhead_ref->remote_url = git__strdup(remote_url);++ *out = fetchhead_ref;++ return 0;+}++static int fetchhead_ref_write(+ git_filebuf *file,+ git_fetchhead_ref *fetchhead_ref)+{+ char oid[GIT_OID_HEXSZ + 1];+ const char *type, *name;++ assert(file && fetchhead_ref);++ git_oid_fmt(oid, &fetchhead_ref->oid);+ oid[GIT_OID_HEXSZ] = '\0';++ if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) {+ type = "branch ";+ name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR);+ } else if(git__prefixcmp(fetchhead_ref->ref_name,+ GIT_REFS_TAGS_DIR) == 0) {+ type = "tag ";+ name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR);+ } else {+ type = "";+ name = fetchhead_ref->ref_name;+ }++ return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n",+ oid,+ (fetchhead_ref->is_merge) ? "" : "not-for-merge",+ type,+ name,+ fetchhead_ref->remote_url);+}++int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)+{+ git_filebuf file = GIT_FILEBUF_INIT;+ git_buf path = GIT_BUF_INIT;+ unsigned int i;+ git_fetchhead_ref *fetchhead_ref;++ assert(repo && fetchhead_refs);++ if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)+ return -1;++ if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE) < 0) {+ git_buf_free(&path);+ return -1;+ }++ git_buf_free(&path);++ git_vector_sort(fetchhead_refs);++ git_vector_foreach(fetchhead_refs, i, fetchhead_ref)+ fetchhead_ref_write(&file, fetchhead_ref);++ return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);+}++static int fetchhead_ref_parse(+ git_oid *oid,+ unsigned int *is_merge,+ git_buf *ref_name,+ const char **remote_url,+ char *line,+ size_t line_num)+{+ char *oid_str, *is_merge_str, *desc, *name = NULL;+ const char *type = NULL;+ int error = 0;++ *remote_url = NULL;++ if (!*line) {+ giterr_set(GITERR_FETCHHEAD,+ "Empty line in FETCH_HEAD line %d", line_num);+ return -1;+ }++ /* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */+ if ((oid_str = git__strsep(&line, "\t")) == NULL) {+ oid_str = line;+ line += strlen(line);++ *is_merge = 1;+ }++ if (strlen(oid_str) != GIT_OID_HEXSZ) {+ giterr_set(GITERR_FETCHHEAD,+ "Invalid object ID in FETCH_HEAD line %d", line_num);+ return -1;+ }++ if (git_oid_fromstr(oid, oid_str) < 0) {+ const git_error *oid_err = giterr_last();+ const char *err_msg = oid_err ? oid_err->message : "Invalid object ID";++ giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %d",+ err_msg, line_num);+ return -1;+ }++ /* Parse new data from newer git clients */+ if (*line) {+ if ((is_merge_str = git__strsep(&line, "\t")) == NULL) {+ giterr_set(GITERR_FETCHHEAD,+ "Invalid description data in FETCH_HEAD line %d", line_num);+ return -1;+ }++ if (*is_merge_str == '\0')+ *is_merge = 1;+ else if (strcmp(is_merge_str, "not-for-merge") == 0)+ *is_merge = 0;+ else {+ giterr_set(GITERR_FETCHHEAD,+ "Invalid for-merge entry in FETCH_HEAD line %d", line_num);+ return -1;+ }++ if ((desc = line) == NULL) {+ giterr_set(GITERR_FETCHHEAD,+ "Invalid description in FETCH_HEAD line %d", line_num);+ return -1;+ }++ if (git__prefixcmp(desc, "branch '") == 0) {+ type = GIT_REFS_HEADS_DIR;+ name = desc + 8;+ } else if (git__prefixcmp(desc, "tag '") == 0) {+ type = GIT_REFS_TAGS_DIR;+ name = desc + 5;+ } else if (git__prefixcmp(desc, "'") == 0)+ name = desc + 1;++ if (name) {+ if ((desc = strchr(name, '\'')) == NULL ||+ git__prefixcmp(desc, "' of ") != 0) {+ giterr_set(GITERR_FETCHHEAD,+ "Invalid description in FETCH_HEAD line %d", line_num);+ return -1;+ }++ *desc = '\0';+ desc += 5;+ }++ *remote_url = desc;+ }++ git_buf_clear(ref_name);++ if (type)+ git_buf_join(ref_name, '/', type, name);+ else if(name)+ git_buf_puts(ref_name, name);++ return error;+}++int git_repository_fetchhead_foreach(git_repository *repo,+ git_repository_fetchhead_foreach_cb cb,+ void *payload)+{+ git_buf path = GIT_BUF_INIT, file = GIT_BUF_INIT, name = GIT_BUF_INIT;+ const char *ref_name;+ git_oid oid;+ const char *remote_url;+ unsigned int is_merge = 0;+ char *buffer, *line;+ size_t line_num = 0;+ int error = 0;++ assert(repo && cb);++ if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)+ return -1;++ if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0)+ goto done;++ buffer = file.ptr;++ while ((line = git__strsep(&buffer, "\n")) != NULL) {+ ++line_num;++ if ((error = fetchhead_ref_parse(&oid, &is_merge, &name, &remote_url,+ line, line_num)) < 0)+ goto done;++ if (git_buf_len(&name) > 0)+ ref_name = git_buf_cstr(&name);+ else+ ref_name = NULL;++ if ((cb(ref_name, remote_url, &oid, is_merge, payload)) != 0) {+ error = GIT_EUSER;+ goto done;+ }+ }++ if (*buffer) {+ giterr_set(GITERR_FETCHHEAD, "No EOL at line %d", line_num+1);+ error = -1;+ goto done;+ }++done:+ git_buf_free(&file);+ git_buf_free(&path);+ git_buf_free(&name);++ return error;+}++void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)+{+ if (fetchhead_ref == NULL)+ return;++ git__free(fetchhead_ref->remote_url);+ git__free(fetchhead_ref->ref_name);+ git__free(fetchhead_ref);+}+
+ libgit2/src/fetchhead.h view
@@ -0,0 +1,34 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_fetchhead_h__+#define INCLUDE_fetchhead_h__++#include "vector.h"++struct git_fetchhead_ref {+ git_oid oid;+ unsigned int is_merge;+ char *ref_name;+ char *remote_url;+};++typedef struct git_fetchhead_ref git_fetchhead_ref;++int git_fetchhead_ref_create(+ git_fetchhead_ref **fetchhead_ref_out,+ git_oid *oid,+ unsigned int is_merge,+ const char *ref_name,+ const char *remote_url);++int git_fetchhead_ref_cmp(const void *a, const void *b);++int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs);++void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref);++#endif
libgit2/src/filebuf.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -50,6 +50,7 @@ if (flags & GIT_FILEBUF_FORCE) p_unlink(file->path_lock); else {+ giterr_clear(); /* actual OS error code just confuses */ giterr_set(GITERR_OS, "Failed to lock file '%s' for writing", file->path_lock); return -1;@@ -72,7 +73,7 @@ if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) { git_file source; char buffer[2048];- size_t read_bytes;+ ssize_t read_bytes; source = p_open(file->path_original, O_RDONLY); if (source < 0) {@@ -82,13 +83,18 @@ return -1; } - while ((read_bytes = p_read(source, buffer, 2048)) > 0) {+ while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { p_write(file->fd, buffer, read_bytes);- if (file->digest)- git_hash_update(file->digest, buffer, read_bytes);+ if (file->compute_digest)+ git_hash_update(&file->digest, buffer, read_bytes); } p_close(source);++ if (read_bytes < 0) {+ giterr_set(GITERR_OS, "Failed to read file '%s'", file->path_original);+ return -1;+ } } return 0;@@ -102,8 +108,10 @@ if (file->fd_is_open && file->path_lock && git_path_exists(file->path_lock)) p_unlink(file->path_lock); - if (file->digest)- git_hash_free_ctx(file->digest);+ if (file->compute_digest) {+ git_hash_ctx_cleanup(&file->digest);+ file->compute_digest = 0;+ } if (file->buffer) git__free(file->buffer);@@ -130,6 +138,11 @@ return result; } +int git_filebuf_flush(git_filebuf *file)+{+ return flush_buffer(file);+}+ static int write_normal(git_filebuf *file, void *source, size_t len) { if (len > 0) {@@ -138,8 +151,8 @@ return -1; } - if (file->digest)- git_hash_update(file->digest, source, len);+ if (file->compute_digest)+ git_hash_update(&file->digest, source, len); } return 0;@@ -175,8 +188,8 @@ assert(zs->avail_in == 0); - if (file->digest)- git_hash_update(file->digest, source, len);+ if (file->compute_digest)+ git_hash_update(&file->digest, source, len); } return 0;@@ -210,8 +223,10 @@ /* If we are hashing on-write, allocate a new hash context */ if (flags & GIT_FILEBUF_HASH_CONTENTS) {- file->digest = git_hash_new_ctx();- GITERR_CHECK_ALLOC(file->digest);+ file->compute_digest = 1;++ if (git_hash_ctx_init(&file->digest) < 0)+ goto cleanup; } compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;@@ -280,16 +295,16 @@ int git_filebuf_hash(git_oid *oid, git_filebuf *file) {- assert(oid && file && file->digest);+ assert(oid && file && file->compute_digest); flush_buffer(file); if (verify_last_error(file) < 0) return -1; - git_hash_final(oid, file->digest);- git_hash_free_ctx(file->digest);- file->digest = NULL;+ git_hash_final(oid, &file->digest);+ git_hash_ctx_cleanup(&file->digest);+ file->compute_digest = 0; return 0; }@@ -314,10 +329,15 @@ if (verify_last_error(file) < 0) goto on_error; - p_close(file->fd);- file->fd = -1; file->fd_is_open = false; + if (p_close(file->fd) < 0) {+ giterr_set(GITERR_OS, "Failed to close file at '%s'", file->path_lock);+ goto on_error;+ }++ file->fd = -1;+ if (p_chmod(file->path_lock, mode)) { giterr_set(GITERR_OS, "Failed to set attributes for file at '%s'", file->path_lock); goto on_error;@@ -450,3 +470,26 @@ return res; } +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file)+{+ int res;+ struct stat st;++ if (file->fd_is_open)+ res = p_fstat(file->fd, &st);+ else+ res = p_stat(file->path_original, &st);++ if (res < 0) {+ giterr_set(GITERR_OS, "Could not get stat info for '%s'",+ file->path_original);+ return res;+ }++ if (mtime)+ *mtime = st.st_mtime;+ if (size)+ *size = (size_t)st.st_size;++ return 0;+}
libgit2/src/filebuf.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -31,7 +31,8 @@ int (*write)(struct git_filebuf *file, void *source, size_t len); - git_hash_ctx *digest;+ bool compute_digest;+ git_hash_ctx digest; unsigned char *buffer; unsigned char *z_buf;@@ -81,5 +82,7 @@ int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode); void git_filebuf_cleanup(git_filebuf *lock); int git_filebuf_hash(git_oid *oid, git_filebuf *file);+int git_filebuf_flush(git_filebuf *file);+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); #endif
libgit2/src/fileops.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,22 +7,15 @@ #include "common.h" #include "fileops.h" #include <ctype.h>+#if GIT_WIN32+#include "win32/findfile.h"+#endif int git_futils_mkpath2file(const char *file_path, const mode_t mode) {- int result = 0;- git_buf target_folder = GIT_BUF_INIT;-- if (git_path_dirname_r(&target_folder, file_path) < 0)- return -1;-- /* Does the containing folder exist? */- if (git_path_isdir(target_folder.ptr) == false)- /* Let's create the tree structure */- result = git_futils_mkdir_r(target_folder.ptr, NULL, mode);-- git_buf_free(&target_folder);- return result;+ return git_futils_mkdir(+ file_path, NULL, mode,+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); } int git_futils_mktmp(git_buf *path_out, const char *filename)@@ -65,11 +58,10 @@ int fd; #ifdef GIT_WIN32- wchar_t* buf;+ wchar_t buf[GIT_WIN_PATH]; - buf = gitwin_to_utf16(path);+ git__utf8_to_16(buf, GIT_WIN_PATH, path); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode);- git__free(buf); #else fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); #endif@@ -94,7 +86,7 @@ { int fd = p_open(path, O_RDONLY); if (fd < 0) {- if (errno == ENOENT)+ if (errno == ENOENT || errno == ENOTDIR) fd = GIT_ENOTFOUND; giterr_set(GITERR_OS, "Failed to open '%s'", path); }@@ -127,11 +119,35 @@ return 0; } -int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated)+int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) {+ ssize_t read_size = 0;++ git_buf_clear(buf);++ if (git_buf_grow(buf, len + 1) < 0)+ return -1;++ /* p_read loops internally to read len bytes */+ read_size = p_read(fd, buf->ptr, len);++ if (read_size != (ssize_t)len) {+ giterr_set(GITERR_OS, "Failed to read descriptor");+ return -1;+ }++ buf->ptr[read_size] = '\0';+ buf->size = read_size;++ return 0;+}++int git_futils_readbuffer_updated(+ git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated)+{ git_file fd;- size_t len; struct stat st;+ bool changed = false; assert(buf && path && *path); @@ -148,41 +164,31 @@ } /*- * If we were given a time, we only want to read the file if it- * has been modified.+ * If we were given a time and/or a size, we only want to read the file+ * if it has been modified. */- if (mtime != NULL && *mtime >= st.st_mtime) {+ if (size && *size != (size_t)st.st_size)+ changed = true;+ if (mtime && *mtime != st.st_mtime)+ changed = true;+ if (!size && !mtime)+ changed = true;++ if (!changed) { p_close(fd); return 0; } if (mtime != NULL) *mtime = st.st_mtime;-- len = (size_t) st.st_size;-- git_buf_clear(buf);+ if (size != NULL)+ *size = (size_t)st.st_size; - if (git_buf_grow(buf, len + 1) < 0) {+ if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { p_close(fd); return -1; } - buf->ptr[len] = '\0';-- while (len > 0) {- ssize_t read_size = p_read(fd, buf->ptr, len);-- if (read_size < 0) {- p_close(fd);- giterr_set(GITERR_OS, "Failed to read descriptor for '%s'", path);- return -1;- }-- len -= read_size;- buf->size += read_size;- }- p_close(fd); if (updated != NULL)@@ -193,7 +199,7 @@ int git_futils_readbuffer(git_buf *buf, const char *path) {- return git_futils_readbuffer_updated(buf, path, NULL, NULL);+ return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL); } int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)@@ -239,237 +245,771 @@ p_munmap(out); } -int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)+int git_futils_mkdir(+ const char *path,+ const char *base,+ mode_t mode,+ uint32_t flags) {- int root_path_offset;+ int error = -1; git_buf make_path = GIT_BUF_INIT;- size_t start;- char *pp, *sp;- bool failed = false;+ ssize_t root = 0;+ char lastch, *tail; - if (base != NULL) {- start = strlen(base);- if (git_buf_joinpath(&make_path, base, path) < 0)- return -1;- } else {- start = 0;- if (git_buf_puts(&make_path, path) < 0)- return -1;+ /* build path and find "root" where we should start calling mkdir */+ if (git_path_join_unrooted(&make_path, path, base, &root) < 0)+ return -1;++ if (make_path.size == 0) {+ giterr_set(GITERR_OS, "Attempt to create empty path");+ goto fail; } - pp = make_path.ptr + start;+ /* remove trailing slashes on path */+ while (make_path.ptr[make_path.size - 1] == '/') {+ make_path.size--;+ make_path.ptr[make_path.size] = '\0';+ } - root_path_offset = git_path_root(make_path.ptr);- if (root_path_offset > 0)- pp += root_path_offset; /* On Windows, will skip the drive name (eg. C: or D:) */+ /* if we are not supposed to made the last element, truncate it */+ if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) {+ git_buf_rtruncate_at_char(&make_path, '/');+ flags |= GIT_MKDIR_SKIP_LAST;+ }+ if ((flags & GIT_MKDIR_SKIP_LAST) != 0)+ git_buf_rtruncate_at_char(&make_path, '/'); - while (!failed && (sp = strchr(pp, '/')) != NULL) {- if (sp != pp && git_path_isdir(make_path.ptr) == false) {- *sp = 0;+ /* if we are not supposed to make the whole path, reset root */+ if ((flags & GIT_MKDIR_PATH) == 0)+ root = git_buf_rfind(&make_path, '/'); - /* Do not choke while trying to recreate an existing directory */- if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)- failed = true;+ /* clip root to make_path length */+ if (root >= (ssize_t)make_path.size)+ root = (ssize_t)make_path.size - 1;+ if (root < 0)+ root = 0; - *sp = '/';+ tail = & make_path.ptr[root];++ while (*tail) {+ /* advance tail to include next path component */+ while (*tail == '/')+ tail++;+ while (*tail && *tail != '/')+ tail++;++ /* truncate path at next component */+ lastch = *tail;+ *tail = '\0';++ /* make directory */+ if (p_mkdir(make_path.ptr, mode) < 0) {+ int already_exists = 0;++ switch (errno) {+ case EEXIST:+ if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0 &&+ !git_path_isdir(make_path.ptr)) {+ giterr_set(+ GITERR_OS, "Existing path is not a directory '%s'",+ make_path.ptr);+ error = GIT_ENOTFOUND;+ goto fail;+ }++ already_exists = 1;+ break;+ case ENOSYS:+ /* Solaris can generate this error if you try to mkdir+ * a path which is already a mount point. In that case,+ * the path does already exist; but it's not implied by+ * the definition of the error, so let's recheck */+ if (git_path_isdir(make_path.ptr)) {+ already_exists = 1;+ break;+ }++ /* Fall through */+ errno = ENOSYS;+ default:+ giterr_set(GITERR_OS, "Failed to make directory '%s'",+ make_path.ptr);+ goto fail;+ }++ if (already_exists && (flags & GIT_MKDIR_EXCL) != 0) {+ giterr_set(GITERR_OS, "Directory already exists '%s'",+ make_path.ptr);+ error = GIT_EEXISTS;+ goto fail;+ } } - pp = sp + 1;- }+ /* chmod if requested */+ if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 ||+ ((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0'))+ {+ if (p_chmod(make_path.ptr, mode) < 0) {+ giterr_set(GITERR_OS, "Failed to set permissions on '%s'",+ make_path.ptr);+ goto fail;+ }+ } - if (*pp != '\0' && !failed) {- if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST)- failed = true;+ *tail = lastch; } git_buf_free(&make_path);+ return 0; - if (failed) {- giterr_set(GITERR_OS,- "Failed to create directory structure at '%s'", path);- return -1;+fail:+ git_buf_free(&make_path);+ return error;+}++int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)+{+ return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);+}++typedef struct {+ const char *base;+ size_t baselen;+ uint32_t flags;+ int error;+} futils__rmdir_data;++static int futils__error_cannot_rmdir(const char *path, const char *filemsg)+{+ if (filemsg)+ giterr_set(GITERR_OS, "Could not remove directory. File '%s' %s",+ path, filemsg);+ else+ giterr_set(GITERR_OS, "Could not remove directory '%s'", path);++ return -1;+}++static int futils__rm_first_parent(git_buf *path, const char *ceiling)+{+ int error = GIT_ENOTFOUND;+ struct stat st;++ while (error == GIT_ENOTFOUND) {+ git_buf_rtruncate_at_char(path, '/');++ if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)+ error = 0;+ else if (p_lstat_posixly(path->ptr, &st) == 0) {+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))+ error = p_unlink(path->ptr);+ else if (!S_ISDIR(st.st_mode))+ error = -1; /* fail to remove non-regular file */+ } else if (errno != ENOTDIR)+ error = -1; } - return 0;+ if (error)+ futils__error_cannot_rmdir(path->ptr, "cannot remove parent");++ return error; } -static int _rmdir_recurs_foreach(void *opaque, git_buf *path)+static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path) {- git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;+ struct stat st;+ futils__rmdir_data *data = opaque; - assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY- || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS- || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);+ if ((data->error = p_lstat_posixly(path->ptr, &st)) < 0) {+ if (errno == ENOENT)+ data->error = 0;+ else if (errno == ENOTDIR) {+ /* asked to remove a/b/c/d/e and a/b is a normal file */+ if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)+ data->error = futils__rm_first_parent(path, data->base);+ else+ futils__error_cannot_rmdir(+ path->ptr, "parent is not directory");+ }+ else+ futils__error_cannot_rmdir(path->ptr, "cannot access");+ } - if (git_path_isdir(path->ptr) == true) {- if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)- return -1;+ else if (S_ISDIR(st.st_mode)) {+ int error = git_path_direach(path, futils__rmdir_recurs_foreach, data);+ if (error < 0)+ return (error == GIT_EUSER) ? data->error : error; - if (p_rmdir(path->ptr) < 0) {- if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST))- return 0;+ data->error = p_rmdir(path->ptr); - giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);- return -1;+ if (data->error < 0) {+ if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&+ (errno == ENOTEMPTY || errno == EEXIST))+ data->error = 0;+ else+ futils__error_cannot_rmdir(path->ptr, NULL); }+ } - return 0;+ else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {+ data->error = p_unlink(path->ptr);++ if (data->error < 0)+ futils__error_cannot_rmdir(path->ptr, "cannot be removed"); } - if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) {- if (p_unlink(path->ptr) < 0) {- giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr);- return -1;- }+ else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)+ data->error = futils__error_cannot_rmdir(path->ptr, "still present"); - return 0;+ return data->error;+}++static int futils__rmdir_empty_parent(void *opaque, git_buf *path)+{+ futils__rmdir_data *data = opaque;+ int error;++ if (git_buf_len(path) <= data->baselen)+ return GIT_ITEROVER;++ error = p_rmdir(git_buf_cstr(path));++ if (error) {+ int en = errno;++ if (en == ENOENT || en == ENOTDIR) {+ giterr_clear();+ error = 0;+ } else if (en == ENOTEMPTY || en == EEXIST) {+ giterr_clear();+ error = GIT_ITEROVER;+ } else {+ futils__error_cannot_rmdir(git_buf_cstr(path), NULL);+ } } - if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) {- giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);+ return error;+}++int git_futils_rmdir_r(+ const char *path, const char *base, uint32_t flags)+{+ int error;+ git_buf fullpath = GIT_BUF_INIT;+ futils__rmdir_data data;++ /* build path and find "root" where we should start calling mkdir */+ if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0) return -1;++ data.base = base ? base : "";+ data.baselen = base ? strlen(base) : 0;+ data.flags = flags;+ data.error = 0;++ error = futils__rmdir_recurs_foreach(&data, &fullpath);++ /* remove now-empty parents if requested */+ if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) {+ error = git_path_walk_up(+ &fullpath, base, futils__rmdir_empty_parent, &data);++ if (error == GIT_ITEROVER)+ error = 0; } - return 0;+ git_buf_free(&fullpath);++ return error; } -int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type)+int git_futils_cleanupdir_r(const char *path) { int error;- git_buf p = GIT_BUF_INIT;+ git_buf fullpath = GIT_BUF_INIT;+ futils__rmdir_data data; - error = git_buf_sets(&p, path);- if (!error)- error = _rmdir_recurs_foreach(&removal_type, &p);- git_buf_free(&p);+ if ((error = git_buf_put(&fullpath, path, strlen(path))) < 0)+ goto clean_up;++ data.base = "";+ data.baselen = 0;+ data.flags = GIT_RMDIR_REMOVE_FILES;+ data.error = 0;++ if (!git_path_exists(path)) {+ giterr_set(GITERR_OS, "Path does not exist: %s" , path);+ error = GIT_ERROR;+ goto clean_up;+ }++ if (!git_path_isdir(path)) {+ giterr_set(GITERR_OS, "Path is not a directory: %s" , path);+ error = GIT_ERROR;+ goto clean_up;+ }++ error = git_path_direach(&fullpath, futils__rmdir_recurs_foreach, &data);+ if (error == GIT_EUSER)+ error = data.error;++clean_up:+ git_buf_free(&fullpath); return error; } -int git_futils_find_global_file(git_buf *path, const char *filename)++static int git_futils_guess_system_dirs(git_buf *out) {- const char *home = getenv("HOME");+#ifdef GIT_WIN32+ return git_win32__find_system_dirs(out);+#else+ return git_buf_sets(out, "/etc");+#endif+} +static int git_futils_guess_global_dirs(git_buf *out)+{ #ifdef GIT_WIN32- if (home == NULL)- home = getenv("USERPROFILE");+ return git_win32__find_global_dirs(out);+#else+ return git_buf_sets(out, getenv("HOME")); #endif+} - if (home == NULL) {- giterr_set(GITERR_OS, "Global file lookup failed. "- "Cannot locate the user's home directory");- return -1;- }+static int git_futils_guess_xdg_dirs(git_buf *out)+{+#ifdef GIT_WIN32+ return git_win32__find_xdg_dirs(out);+#else+ const char *env = NULL; - if (git_buf_joinpath(path, home, filename) < 0)- return -1;+ if ((env = getenv("XDG_CONFIG_HOME")) != NULL)+ return git_buf_joinpath(out, env, "git");+ else if ((env = getenv("HOME")) != NULL)+ return git_buf_joinpath(out, env, ".config/git"); - if (git_path_exists(path->ptr) == false) {- git_buf_clear(path);- return GIT_ENOTFOUND;+ git_buf_clear(out);+ return 0;+#endif+}++typedef int (*git_futils_dirs_guess_cb)(git_buf *out);++static git_buf git_futils__dirs[GIT_FUTILS_DIR__MAX] =+ { GIT_BUF_INIT, GIT_BUF_INIT, GIT_BUF_INIT };++static git_futils_dirs_guess_cb git_futils__dir_guess[GIT_FUTILS_DIR__MAX] = {+ git_futils_guess_system_dirs,+ git_futils_guess_global_dirs,+ git_futils_guess_xdg_dirs,+};++static int git_futils_check_selector(git_futils_dir_t which)+{+ if (which < GIT_FUTILS_DIR__MAX)+ return 0;+ giterr_set(GITERR_INVALID, "config directory selector out of range");+ return -1;+}++int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which)+{+ assert(out);++ *out = NULL;++ GITERR_CHECK_ERROR(git_futils_check_selector(which));++ if (!git_buf_len(&git_futils__dirs[which]))+ GITERR_CHECK_ERROR(+ git_futils__dir_guess[which](&git_futils__dirs[which]));++ *out = &git_futils__dirs[which];+ return 0;+}++int git_futils_dirs_get_str(char *out, size_t outlen, git_futils_dir_t which)+{+ const git_buf *path = NULL;++ GITERR_CHECK_ERROR(git_futils_check_selector(which));+ GITERR_CHECK_ERROR(git_futils_dirs_get(&path, which));++ if (!out || path->size >= outlen) {+ giterr_set(GITERR_NOMEMORY, "Buffer is too short for the path");+ return GIT_EBUFS; } + git_buf_copy_cstr(out, outlen, path); return 0; } -#ifdef GIT_WIN32-typedef struct {- wchar_t *path;- DWORD len;-} win32_path;+#define PATH_MAGIC "$PATH" -static const win32_path *win32_system_root(void)+int git_futils_dirs_set(git_futils_dir_t which, const char *search_path) {- static win32_path s_root = { 0, 0 };+ const char *expand_path = NULL;+ git_buf merge = GIT_BUF_INIT; - if (s_root.path == NULL) {- const wchar_t *root_tmpl = L"%PROGRAMFILES%\\Git\\etc\\";+ GITERR_CHECK_ERROR(git_futils_check_selector(which)); - s_root.len = ExpandEnvironmentStringsW(root_tmpl, NULL, 0);- if (s_root.len <= 0) {- giterr_set(GITERR_OS, "Failed to expand environment strings");- return NULL;- }+ if (search_path != NULL)+ expand_path = strstr(search_path, PATH_MAGIC); - s_root.path = git__calloc(s_root.len, sizeof(wchar_t));- if (s_root.path == NULL)- return NULL;+ /* init with default if not yet done and needed (ignoring error) */+ if ((!search_path || expand_path) &&+ !git_buf_len(&git_futils__dirs[which]))+ git_futils__dir_guess[which](&git_futils__dirs[which]); - if (ExpandEnvironmentStringsW(root_tmpl, s_root.path, s_root.len) != s_root.len) {- giterr_set(GITERR_OS, "Failed to expand environment strings");- git__free(s_root.path);- s_root.path = NULL;- return NULL;- }- }+ /* if $PATH is not referenced, then just set the path */+ if (!expand_path)+ return git_buf_sets(&git_futils__dirs[which], search_path); - return &s_root;+ /* otherwise set to join(before $PATH, old value, after $PATH) */+ if (expand_path > search_path)+ git_buf_set(&merge, search_path, expand_path - search_path);++ if (git_buf_len(&git_futils__dirs[which]))+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR,+ merge.ptr, git_futils__dirs[which].ptr);++ expand_path += strlen(PATH_MAGIC);+ if (*expand_path)+ git_buf_join(&merge, GIT_PATH_LIST_SEPARATOR, merge.ptr, expand_path);++ git_buf_swap(&git_futils__dirs[which], &merge);+ git_buf_free(&merge);++ return git_buf_oom(&git_futils__dirs[which]) ? -1 : 0; } -static int win32_find_system_file(git_buf *path, const char *filename)+void git_futils_dirs_free(void) {- int error = 0;- const win32_path *root = win32_system_root();+ int i;+ for (i = 0; i < GIT_FUTILS_DIR__MAX; ++i)+ git_buf_free(&git_futils__dirs[i]);+}++static int git_futils_find_in_dirlist(+ git_buf *path, const char *name, git_futils_dir_t which, const char *label)+{ size_t len;- wchar_t *file_utf16 = NULL, *scan;- char *file_utf8 = NULL;+ const char *scan, *next = NULL;+ const git_buf *syspath; - if (!root || !filename || (len = strlen(filename)) == 0)- return GIT_ENOTFOUND;+ GITERR_CHECK_ERROR(git_futils_dirs_get(&syspath, which)); - /* allocate space for wchar_t path to file */- file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t));- GITERR_CHECK_ALLOC(file_utf16);+ for (scan = git_buf_cstr(syspath); scan; scan = next) {+ for (next = strchr(scan, GIT_PATH_LIST_SEPARATOR);+ next && next > scan && next[-1] == '\\';+ next = strchr(next + 1, GIT_PATH_LIST_SEPARATOR))+ /* find unescaped separator or end of string */; - /* append root + '\\' + filename as wchar_t */- memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));+ len = next ? (size_t)(next++ - scan) : strlen(scan);+ if (!len)+ continue; - if (*filename == '/' || *filename == '\\')- filename++;+ GITERR_CHECK_ERROR(git_buf_set(path, scan, len));+ GITERR_CHECK_ERROR(git_buf_joinpath(path, path->ptr, name)); - if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) !=- (int)len + 1) {- error = -1;- goto cleanup;+ if (git_path_exists(path->ptr))+ return 0; } - for (scan = file_utf16; *scan; scan++)- if (*scan == L'/')- *scan = L'\\';+ git_buf_clear(path);+ giterr_set(GITERR_OS, "The %s file '%s' doesn't exist", label, name);+ return GIT_ENOTFOUND;+} - /* check access */- if (_waccess(file_utf16, F_OK) < 0) {- error = GIT_ENOTFOUND;- goto cleanup;+int git_futils_find_system_file(git_buf *path, const char *filename)+{+ return git_futils_find_in_dirlist(+ path, filename, GIT_FUTILS_DIR_SYSTEM, "system");+}++int git_futils_find_global_file(git_buf *path, const char *filename)+{+ return git_futils_find_in_dirlist(+ path, filename, GIT_FUTILS_DIR_GLOBAL, "global");+}++int git_futils_find_xdg_file(git_buf *path, const char *filename)+{+ return git_futils_find_in_dirlist(+ path, filename, GIT_FUTILS_DIR_XDG, "global/xdg");+}++int git_futils_fake_symlink(const char *old, const char *new)+{+ int retcode = GIT_ERROR;+ int fd = git_futils_creat_withpath(new, 0755, 0644);+ if (fd >= 0) {+ retcode = p_write(fd, old, strlen(old));+ p_close(fd); }+ return retcode;+} - /* convert to utf8 */- if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL)+static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done)+{+ int error = 0;+ char buffer[4096];+ ssize_t len = 0;++ while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0)+ /* p_write() does not have the same semantics as write(). It loops+ * internally and will return 0 when it has completed writing.+ */+ error = p_write(ofd, buffer, len);++ if (len < 0) {+ giterr_set(GITERR_OS, "Read error while copying file");+ error = (int)len;+ }++ if (close_fd_when_done) {+ p_close(ifd);+ p_close(ofd);+ }++ return error;+}++int git_futils_cp(const char *from, const char *to, mode_t filemode)+{+ int ifd, ofd;++ if ((ifd = git_futils_open_ro(from)) < 0)+ return ifd;++ if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) {+ if (errno == ENOENT || errno == ENOTDIR)+ ofd = GIT_ENOTFOUND;+ giterr_set(GITERR_OS, "Failed to open '%s' for writing", to);+ p_close(ifd);+ return ofd;+ }++ return cp_by_fd(ifd, ofd, true);+}++static int cp_link(const char *from, const char *to, size_t link_size)+{+ int error = 0;+ ssize_t read_len;+ char *link_data = git__malloc(link_size + 1);+ GITERR_CHECK_ALLOC(link_data);++ read_len = p_readlink(from, link_data, link_size);+ if (read_len != (ssize_t)link_size) {+ giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", from); error = -1;+ } else {- git_path_mkposix(file_utf8);- git_buf_attach(path, file_utf8, 0);+ link_data[read_len] = '\0';++ if (p_symlink(link_data, to) < 0) {+ giterr_set(GITERR_OS, "Could not symlink '%s' as '%s'",+ link_data, to);+ error = -1;+ } } -cleanup:- git__free(file_utf16);+ git__free(link_data);+ return error;+} +typedef struct {+ const char *to_root;+ git_buf to;+ ssize_t from_prefix;+ uint32_t flags;+ uint32_t mkdir_flags;+ mode_t dirmode;+} cp_r_info;++#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10)++static int _cp_r_mkdir(cp_r_info *info, git_buf *from)+{+ int error = 0;++ /* create root directory the first time we need to create a directory */+ if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) {+ error = git_futils_mkdir(+ info->to_root, NULL, info->dirmode,+ (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0);++ info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT;+ }++ /* create directory with root as base to prevent excess chmods */+ if (!error)+ error = git_futils_mkdir(+ from->ptr + info->from_prefix, info->to_root,+ info->dirmode, info->mkdir_flags);+ return error; }-#endif -int git_futils_find_system_file(git_buf *path, const char *filename)+static int _cp_r_callback(void *ref, git_buf *from) {- if (git_buf_joinpath(path, "/etc", filename) < 0)+ int error = 0;+ cp_r_info *info = ref;+ struct stat from_st, to_st;+ bool exists = false;++ if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 &&+ from->ptr[git_path_basename_offset(from)] == '.')+ return 0;++ if (git_buf_joinpath(+ &info->to, info->to_root, from->ptr + info->from_prefix) < 0) return -1; - if (git_path_exists(path->ptr) == true)+ if (p_lstat(info->to.ptr, &to_st) < 0) {+ if (errno != ENOENT && errno != ENOTDIR) {+ giterr_set(GITERR_OS,+ "Could not access %s while copying files", info->to.ptr);+ return -1;+ }+ } else+ exists = true;++ if ((error = git_path_lstat(from->ptr, &from_st)) < 0)+ return error;++ if (S_ISDIR(from_st.st_mode)) {+ mode_t oldmode = info->dirmode;++ /* if we are not chmod'ing, then overwrite dirmode */+ if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0)+ info->dirmode = from_st.st_mode;++ /* make directory now if CREATE_EMPTY_DIRS is requested and needed */+ if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0)+ error = _cp_r_mkdir(info, from);++ /* recurse onto target directory */+ if (!error && (!exists || S_ISDIR(to_st.st_mode)))+ error = git_path_direach(from, _cp_r_callback, info);++ if (oldmode != 0)+ info->dirmode = oldmode;++ return error;+ }++ if (exists) {+ if ((info->flags & GIT_CPDIR_OVERWRITE) == 0)+ return 0;++ if (p_unlink(info->to.ptr) < 0) {+ giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'",+ info->to.ptr);+ return -1;+ }+ }++ /* Done if this isn't a regular file or a symlink */+ if (!S_ISREG(from_st.st_mode) &&+ (!S_ISLNK(from_st.st_mode) ||+ (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0)) return 0; - git_buf_clear(path);+ /* Make container directory on demand if needed */+ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 &&+ (error = _cp_r_mkdir(info, from)) < 0)+ return error; -#ifdef GIT_WIN32- return win32_find_system_file(path, filename);-#else- return GIT_ENOTFOUND;-#endif+ /* make symlink or regular file */+ if (S_ISLNK(from_st.st_mode))+ error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size);+ else {+ mode_t usemode = from_st.st_mode;++ if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0)+ usemode = (usemode & 0111) ? 0777 : 0666;++ error = git_futils_cp(from->ptr, info->to.ptr, usemode);+ }++ return error;+}++int git_futils_cp_r(+ const char *from,+ const char *to,+ uint32_t flags,+ mode_t dirmode)+{+ int error;+ git_buf path = GIT_BUF_INIT;+ cp_r_info info;++ if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */+ return -1;++ info.to_root = to;+ info.flags = flags;+ info.dirmode = dirmode;+ info.from_prefix = path.size;+ git_buf_init(&info.to, 0);++ /* precalculate mkdir flags */+ if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) {+ /* if not creating empty dirs, then use mkdir to create the path on+ * demand right before files are copied.+ */+ info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST;+ if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0)+ info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH;+ } else {+ /* otherwise, we will do simple mkdir as directories are encountered */+ info.mkdir_flags =+ ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0;+ }++ error = _cp_r_callback(&info, &path);++ git_buf_free(&path);+ git_buf_free(&info.to);++ return error;+}++int git_futils_filestamp_check(+ git_futils_filestamp *stamp, const char *path)+{+ struct stat st;++ /* if the stamp is NULL, then always reload */+ if (stamp == NULL)+ return 1;++ if (p_stat(path, &st) < 0)+ return GIT_ENOTFOUND;++ if (stamp->mtime == (git_time_t)st.st_mtime &&+ stamp->size == (git_off_t)st.st_size &&+ stamp->ino == (unsigned int)st.st_ino)+ return 0;++ stamp->mtime = (git_time_t)st.st_mtime;+ stamp->size = (git_off_t)st.st_size;+ stamp->ino = (unsigned int)st.st_ino;++ return 1;+}++void git_futils_filestamp_set(+ git_futils_filestamp *target, const git_futils_filestamp *source)+{+ assert(target);++ if (source)+ memcpy(target, source, sizeof(*target));+ else+ memset(target, 0, sizeof(*target)); }
libgit2/src/fileops.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -18,7 +18,9 @@ * Read whole files into an in-memory buffer for processing */ extern int git_futils_readbuffer(git_buf *obj, const char *path);-extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated);+extern int git_futils_readbuffer_updated(+ git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated);+extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len); /** * File utils@@ -49,34 +51,99 @@ /** * Create a path recursively+ *+ * If a base parameter is being passed, it's expected to be valued with a+ * path pointing to an already existing directory. */ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode); /**+ * Flags to pass to `git_futils_mkdir`.+ *+ * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists.+ * * GIT_MKDIR_PATH says to make all components in the path.+ * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation+ * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path+ * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path+ * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path+ * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST+ *+ * Note that the chmod options will be executed even if the directory already+ * exists, unless GIT_MKDIR_EXCL is given.+ */+typedef enum {+ GIT_MKDIR_EXCL = 1,+ GIT_MKDIR_PATH = 2,+ GIT_MKDIR_CHMOD = 4,+ GIT_MKDIR_CHMOD_PATH = 8,+ GIT_MKDIR_SKIP_LAST = 16,+ GIT_MKDIR_SKIP_LAST2 = 32,+ GIT_MKDIR_VERIFY_DIR = 64,+} git_futils_mkdir_flags;++/**+ * Create a directory or entire path.+ *+ * This makes a directory (and the entire path leading up to it if requested),+ * and optionally chmods the directory immediately after (or each part of the+ * path if requested).+ *+ * @param path The path to create.+ * @param base Root for relative path. These directories will never be made.+ * @param mode The mode to use for created directories.+ * @param flags Combination of the mkdir flags above.+ * @return 0 on success, else error code+ */+extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags);++/** * Create all the folders required to contain * the full path of a file */ extern int git_futils_mkpath2file(const char *path, const mode_t mode); +/**+ * Flags to pass to `git_futils_rmdir_r`.+ *+ * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty+ * dirs and generate error if any files are found.+ * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy.+ * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error.+ * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base+ * if removing this item leaves them empty+ * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR+ *+ * The old values translate into the new as follows:+ *+ * * GIT_DIRREMOVAL_EMPTY_HIERARCHY == GIT_RMDIR_EMPTY_HIERARCHY+ * * GIT_DIRREMOVAL_FILES_AND_DIRS ~= GIT_RMDIR_REMOVE_FILES+ * * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS == GIT_RMDIR_SKIP_NONEMPTY+ */ typedef enum {- GIT_DIRREMOVAL_EMPTY_HIERARCHY = 0,- GIT_DIRREMOVAL_FILES_AND_DIRS = 1,- GIT_DIRREMOVAL_ONLY_EMPTY_DIRS = 2,-} git_directory_removal_type;+ GIT_RMDIR_EMPTY_HIERARCHY = 0,+ GIT_RMDIR_REMOVE_FILES = (1 << 0),+ GIT_RMDIR_SKIP_NONEMPTY = (1 << 1),+ GIT_RMDIR_EMPTY_PARENTS = (1 << 2),+ GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3),+} git_futils_rmdir_flags; /** * Remove path and any files and directories beneath it. *- * @param path Path to to top level directory to process.- *- * @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy- * of empty directories (will fail if any file is found), GIT_DIRREMOVAL_FILES_AND_DIRS- * to remove a hierarchy of files and folders, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove- * empty directories (no failure on file encounter).+ * @param path Path to the top level directory to process.+ * @param base Root for relative path.+ * @param flags Combination of git_futils_rmdir_flags values+ * @return 0 on success; -1 on error.+ */+extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);++/**+ * Remove all files and directories beneath the specified path. *+ * @param path Path to the top level directory to process. * @return 0 on success; -1 on error. */-extern int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type);+extern int git_futils_cleanupdir_r(const char *path); /** * Create and open a temporary file with a `_git2_` suffix.@@ -92,6 +159,58 @@ extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); /**+ * Copy a file+ *+ * The filemode will be used for the newly created file.+ */+extern int git_futils_cp(+ const char *from,+ const char *to,+ mode_t filemode);++/**+ * Flags that can be passed to `git_futils_cp_r`.+ *+ * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no+ * files under them (otherwise directories will only be created lazily+ * when a file inside them is copied).+ * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored.+ * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored.+ * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content,+ * otherwise they are silently skipped.+ * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode`+ * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the+ * source file to the target; with this flag, always use 0666 (or 0777 if+ * source has exec bits set) for target.+ */+typedef enum {+ GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0),+ GIT_CPDIR_COPY_SYMLINKS = (1u << 1),+ GIT_CPDIR_COPY_DOTFILES = (1u << 2),+ GIT_CPDIR_OVERWRITE = (1u << 3),+ GIT_CPDIR_CHMOD_DIRS = (1u << 4),+ GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5),+} git_futils_cpdir_flags;++/**+ * Copy a directory tree.+ *+ * This copies directories and files from one root to another. You can+ * pass a combinationof GIT_CPDIR flags as defined above.+ *+ * If you pass the CHMOD flag, then the dirmode will be applied to all+ * directories that are created during the copy, overiding the natural+ * permissions. If you do not pass the CHMOD flag, then the dirmode+ * will actually be copied from the source files and the `dirmode` arg+ * will be ignored.+ */+extern int git_futils_cp_r(+ const char *from,+ const char *to,+ uint32_t flags,+ mode_t dirmode);++/** * Open a file readonly and set error if needed. */ extern int git_futils_open_ro(const char *path);@@ -157,23 +276,120 @@ * * @param pathbuf buffer to write the full path into * @param filename name of file to find in the home directory- * @return- * - 0 if found;- * - GIT_ENOTFOUND if not found;- * - -1 on an unspecified OS related error.+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error */ extern int git_futils_find_global_file(git_buf *path, const char *filename); /**+ * Find an "XDG" file (i.e. one in user's XDG config path).+ *+ * @param pathbuf buffer to write the full path into+ * @param filename name of file to find in the home directory+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error+ */+extern int git_futils_find_xdg_file(git_buf *path, const char *filename);++/** * Find a "system" file (i.e. one shared for all users of the system). * * @param pathbuf buffer to write the full path into * @param filename name of file to find in the home directory- * @return- * - 0 if found;- * - GIT_ENOTFOUND if not found;- * - -1 on an unspecified OS related error.+ * @return 0 if found, GIT_ENOTFOUND if not found, or -1 on other OS error */ extern int git_futils_find_system_file(git_buf *path, const char *filename);++typedef enum {+ GIT_FUTILS_DIR_SYSTEM = 0,+ GIT_FUTILS_DIR_GLOBAL = 1,+ GIT_FUTILS_DIR_XDG = 2,+ GIT_FUTILS_DIR__MAX = 3,+} git_futils_dir_t;++/**+ * Get the search path for global/system/xdg files+ *+ * @param out pointer to git_buf containing search path+ * @param which which list of paths to return+ * @return 0 on success, <0 on failure+ */+extern int git_futils_dirs_get(const git_buf **out, git_futils_dir_t which);++/**+ * Get search path into a preallocated buffer+ *+ * @param out String buffer to write into+ * @param outlen Size of string buffer+ * @param which Which search path to return+ * @return 0 on success, GIT_EBUFS if out is too small, <0 on other failure+ */++extern int git_futils_dirs_get_str(+ char *out, size_t outlen, git_futils_dir_t which);++/**+ * Set search paths for global/system/xdg files+ *+ * The first occurrence of the magic string "$PATH" in the new value will+ * be replaced with the old value of the search path.+ *+ * @param which Which search path to modify+ * @param paths New search path (separated by GIT_PATH_LIST_SEPARATOR)+ * @return 0 on success, <0 on failure (allocation error)+ */+extern int git_futils_dirs_set(git_futils_dir_t which, const char *paths);++/**+ * Release / reset all search paths+ */+extern void git_futils_dirs_free(void);++/**+ * Create a "fake" symlink (text file containing the target path).+ *+ * @param new symlink file to be created+ * @param old original symlink target+ * @return 0 on success, -1 on error+ */+extern int git_futils_fake_symlink(const char *new, const char *old);++/**+ * A file stamp represents a snapshot of information about a file that can+ * be used to test if the file changes. This portable implementation is+ * based on stat data about that file, but it is possible that OS specific+ * versions could be implemented in the future.+ */+typedef struct {+ git_time_t mtime;+ git_off_t size;+ unsigned int ino;+} git_futils_filestamp;++/**+ * Compare stat information for file with reference info.+ *+ * This function updates the file stamp to current data for the given path+ * and returns 0 if the file is up-to-date relative to the prior setting or+ * 1 if the file has been changed. (This also may return GIT_ENOTFOUND if+ * the file doesn't exist.)+ *+ * @param stamp File stamp to be checked+ * @param path Path to stat and check if changed+ * @return 0 if up-to-date, 1 if out-of-date, <0 on error+ */+extern int git_futils_filestamp_check(+ git_futils_filestamp *stamp, const char *path);++/**+ * Set or reset file stamp data+ *+ * This writes the target file stamp. If the source is NULL, this will set+ * the target stamp to values that will definitely be out of date. If the+ * source is not NULL, this copies the source values to the target.+ *+ * @param tgt File stamp to write to+ * @param src File stamp to copy from or NULL to clear the target+ */+extern void git_futils_filestamp_set(+ git_futils_filestamp *tgt, const git_futils_filestamp *src); #endif /* INCLUDE_fileops_h__ */
libgit2/src/filter.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -11,79 +11,7 @@ #include "filter.h" #include "repository.h" #include "git2/config.h"--/* Tweaked from Core Git. I wonder what we could use this for... */-void git_text_gather_stats(git_text_stats *stats, const git_buf *text)-{- size_t i;-- memset(stats, 0, sizeof(*stats));-- for (i = 0; i < git_buf_len(text); i++) {- unsigned char c = text->ptr[i];-- if (c == '\r') {- stats->cr++;-- if (i + 1 < git_buf_len(text) && text->ptr[i + 1] == '\n')- stats->crlf++;- }-- else if (c == '\n')- stats->lf++;-- else if (c == 0x85)- /* Unicode CR+LF */- stats->crlf++;-- else if (c == 127)- /* DEL */- stats->nonprintable++;-- else if (c <= 0x1F || (c >= 0x80 && c <= 0x9F)) {- switch (c) {- /* BS, HT, ESC and FF */- case '\b': case '\t': case '\033': case '\014':- stats->printable++;- break;- case 0:- stats->nul++;- /* fall through */- default:- stats->nonprintable++;- }- }-- else- stats->printable++;- }-- /* If file ends with EOF then don't count this EOF as non-printable. */- if (git_buf_len(text) >= 1 && text->ptr[text->size - 1] == '\032')- stats->nonprintable--;-}--/*- * Fresh from Core Git- */-int git_text_is_binary(git_text_stats *stats)-{- if (stats->nul)- return 1;-- if ((stats->printable >> 7) < stats->nonprintable)- return 1;- /*- * Other heuristics? Average line length might be relevant,- * as might LF vs CR vs CRLF counts..- *- * NOTE! It might be normal to have a low ratio of CRLF to LF- * (somebody starts with a LF-only file and edits it with an editor- * that adds CRLF only to lines that are added..). But do we- * want to support CR-only? Probably not.- */- return 0;-}+#include "blob.h" int git_filters_load(git_vector *filters, git_repository *repo, const char *path, int mode) {@@ -95,8 +23,9 @@ if (error < 0) return error; } else {- giterr_set(GITERR_INVALID, "Worktree filters are not implemented yet");- return -1;+ error = git_filter_add__crlf_to_workdir(filters, repo, path);+ if (error < 0)+ return error; } return (int)filters->length;@@ -119,7 +48,8 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) {- unsigned int i, src;+ size_t i;+ unsigned int src; git_buf *dbuffer[2]; dbuffer[0] = source;@@ -162,4 +92,3 @@ return 0; }-
libgit2/src/filter.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -31,14 +31,6 @@ GIT_CRLF_AUTO, } git_crlf_t; -typedef struct {- /* NUL, CR, LF and CRLF counts */- unsigned int nul, cr, lf, crlf;-- /* These are just approximations! */- unsigned int printable, nonprintable;-} git_text_stats;- /* * FILTER API */@@ -96,24 +88,7 @@ /* Strip CRLF, from Worktree to ODB */ extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path); --/*- * PLAINTEXT API- */--/*- * Gather stats for a piece of text- *- * Fill the `stats` structure with information on the number of- * unreadable characters, carriage returns, etc, so it can be- * used in heuristics.- */-extern void git_text_gather_stats(git_text_stats *stats, const git_buf *text);--/*- * Process `git_text_stats` data generated by `git_text_stat` to see- * if it qualifies as a binary file- */-extern int git_text_is_binary(git_text_stats *stats);+/* Add CRLF, from ODB to worktree */+extern int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path); #endif
+ libgit2/src/fnmatch.c view
@@ -0,0 +1,192 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++/*+ * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.+ * Compares a filename or pathname to a pattern.+ */++#include <ctype.h>+#include <stdio.h>+#include <string.h>++#include "fnmatch.h"++#define EOS '\0'++#define RANGE_MATCH 1+#define RANGE_NOMATCH 0+#define RANGE_ERROR (-1)++static int rangematch(const char *, char, int, char **);++static int+p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)+{+ const char *stringstart;+ char *newp;+ char c, test;++ if (recurs-- == 0)+ return FNM_NORES;++ for (stringstart = string;;)+ switch (c = *pattern++) {+ case EOS:+ if ((flags & FNM_LEADING_DIR) && *string == '/')+ return (0);+ return (*string == EOS ? 0 : FNM_NOMATCH);+ case '?':+ if (*string == EOS)+ return (FNM_NOMATCH);+ if (*string == '/' && (flags & FNM_PATHNAME))+ return (FNM_NOMATCH);+ if (*string == '.' && (flags & FNM_PERIOD) &&+ (string == stringstart ||+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))+ return (FNM_NOMATCH);+ ++string;+ break;+ case '*':+ c = *pattern;+ /* Collapse multiple stars. */+ while (c == '*')+ c = *++pattern;++ if (*string == '.' && (flags & FNM_PERIOD) &&+ (string == stringstart ||+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))+ return (FNM_NOMATCH);++ /* Optimize for pattern with * at end or before /. */+ if (c == EOS) {+ if (flags & FNM_PATHNAME)+ return ((flags & FNM_LEADING_DIR) ||+ strchr(string, '/') == NULL ?+ 0 : FNM_NOMATCH);+ else+ return (0);+ } else if (c == '/' && (flags & FNM_PATHNAME)) {+ if ((string = strchr(string, '/')) == NULL)+ return (FNM_NOMATCH);+ break;+ }++ /* General case, use recursion. */+ while ((test = *string) != EOS) {+ int e;++ e = p_fnmatchx(pattern, string, flags & ~FNM_PERIOD, recurs);+ if (e != FNM_NOMATCH)+ return e;+ if (test == '/' && (flags & FNM_PATHNAME))+ break;+ ++string;+ }+ return (FNM_NOMATCH);+ case '[':+ if (*string == EOS)+ return (FNM_NOMATCH);+ if (*string == '/' && (flags & FNM_PATHNAME))+ return (FNM_NOMATCH);+ if (*string == '.' && (flags & FNM_PERIOD) &&+ (string == stringstart ||+ ((flags & FNM_PATHNAME) && *(string - 1) == '/')))+ return (FNM_NOMATCH);++ switch (rangematch(pattern, *string, flags, &newp)) {+ case RANGE_ERROR:+ /* not a good range, treat as normal text */+ goto normal;+ case RANGE_MATCH:+ pattern = newp;+ break;+ case RANGE_NOMATCH:+ return (FNM_NOMATCH);+ }+ ++string;+ break;+ case '\\':+ if (!(flags & FNM_NOESCAPE)) {+ if ((c = *pattern++) == EOS) {+ c = '\\';+ --pattern;+ }+ }+ /* FALLTHROUGH */+ default:+ normal:+ if (c != *string && !((flags & FNM_CASEFOLD) &&+ (tolower((unsigned char)c) ==+ tolower((unsigned char)*string))))+ return (FNM_NOMATCH);+ ++string;+ break;+ }+ /* NOTREACHED */+}++static int+rangematch(const char *pattern, char test, int flags, char **newp)+{+ int negate, ok;+ char c, c2;++ /*+ * A bracket expression starting with an unquoted circumflex+ * character produces unspecified results (IEEE 1003.2-1992,+ * 3.13.2). This implementation treats it like '!', for+ * consistency with the regular expression syntax.+ * J.T. Conklin (conklin@ngai.kaleida.com)+ */+ if ((negate = (*pattern == '!' || *pattern == '^')) != 0)+ ++pattern;++ if (flags & FNM_CASEFOLD)+ test = (char)tolower((unsigned char)test);++ /*+ * A right bracket shall lose its special meaning and represent+ * itself in a bracket expression if it occurs first in the list.+ * -- POSIX.2 2.8.3.2+ */+ ok = 0;+ c = *pattern++;+ do {+ if (c == '\\' && !(flags & FNM_NOESCAPE))+ c = *pattern++;+ if (c == EOS)+ return (RANGE_ERROR);+ if (c == '/' && (flags & FNM_PATHNAME))+ return (RANGE_NOMATCH);+ if ((flags & FNM_CASEFOLD))+ c = (char)tolower((unsigned char)c);+ if (*pattern == '-'+ && (c2 = *(pattern+1)) != EOS && c2 != ']') {+ pattern += 2;+ if (c2 == '\\' && !(flags & FNM_NOESCAPE))+ c2 = *pattern++;+ if (c2 == EOS)+ return (RANGE_ERROR);+ if (flags & FNM_CASEFOLD)+ c2 = (char)tolower((unsigned char)c2);+ if (c <= test && test <= c2)+ ok = 1;+ } else if (c == test)+ ok = 1;+ } while ((c = *pattern++) != ']');++ *newp = (char *)pattern;+ return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);+}++int+p_fnmatch(const char *pattern, const char *string, int flags)+{+ return p_fnmatchx(pattern, string, flags, 64);+}+
+ libgit2/src/fnmatch.h view
@@ -0,0 +1,28 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_fnmatch__compat_h__+#define INCLUDE_fnmatch__compat_h__++#include "common.h"++#define FNM_NOMATCH 1 /* Match failed. */+#define FNM_NOSYS 2 /* Function not supported (unused). */+#define FNM_NORES 3 /* Out of resources */++#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */+#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */+#define FNM_PERIOD 0x04 /* Period must be matched by period. */+#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */+#define FNM_CASEFOLD 0x10 /* Case insensitive search. */++#define FNM_IGNORECASE FNM_CASEFOLD+#define FNM_FILE_NAME FNM_PATHNAME++extern int p_fnmatch(const char *pattern, const char *string, int flags);++#endif /* _FNMATCH_H */+
libgit2/src/global.c view
@@ -1,14 +1,19 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "common.h" #include "global.h"-#include "git2/threads.h" +#include "hash.h"+#include "fileops.h"+#include "git2/threads.h" #include "thread-utils.h" ++git_mutex git__mwindow_mutex;+ /** * Handle the global state with TLS *@@ -35,30 +40,58 @@ * functions are not available in that case. */ +/*+ * `git_threads_init()` allows subsystems to perform global setup,+ * which may take place in the global scope. An explicit memory+ * fence exists at the exit of `git_threads_init()`. Without this,+ * CPU cores are free to reorder cache invalidation of `_tls_init`+ * before cache invalidation of the subsystems' newly written global+ * state.+ */ #if defined(GIT_THREADS) && defined(GIT_WIN32) static DWORD _tls_index; static int _tls_init = 0; -void git_threads_init(void)+int git_threads_init(void) {+ int error;+ if (_tls_init)- return;+ return 0; _tls_index = TlsAlloc();- _tls_init = 1;+ git_mutex_init(&git__mwindow_mutex);++ /* Initialize any other subsystems that have global state */+ if ((error = git_hash_global_init()) >= 0)+ _tls_init = 1;++ if (error == 0)+ _tls_init = 1;++ GIT_MEMORY_BARRIER;++ return error; } void git_threads_shutdown(void) { TlsFree(_tls_index); _tls_init = 0;+ git_mutex_free(&git__mwindow_mutex);++ /* Shut down any subsystems that have global state */+ git_hash_global_shutdown();+ git_futils_dirs_free(); } git_global_st *git__global_state(void) { void *ptr; + assert(_tls_init);+ if ((ptr = TlsGetValue(_tls_index)) != NULL) return ptr; @@ -81,25 +114,42 @@ git__free(st); } -void git_threads_init(void)+int git_threads_init(void) {+ int error = 0;+ if (_tls_init)- return;+ return 0; + git_mutex_init(&git__mwindow_mutex); pthread_key_create(&_tls_key, &cb__free_status);- _tls_init = 1;++ /* Initialize any other subsystems that have global state */+ if ((error = git_hash_global_init()) >= 0)+ _tls_init = 1;++ GIT_MEMORY_BARRIER;++ return error; } void git_threads_shutdown(void) { pthread_key_delete(_tls_key); _tls_init = 0;+ git_mutex_free(&git__mwindow_mutex);++ /* Shut down any subsystems that have global state */+ git_hash_global_shutdown();+ git_futils_dirs_free(); } git_global_st *git__global_state(void) { void *ptr; + assert(_tls_init);+ if ((ptr = pthread_getspecific(_tls_key)) != NULL) return ptr; @@ -116,14 +166,17 @@ static git_global_st __state; -void git_threads_init(void)+int git_threads_init(void) { /* noop */ + return 0; } void git_threads_shutdown(void) {- /* noop */+ /* Shut down any subsystems that have global state */+ git_hash_global_shutdown();+ git_futils_dirs_free(); } git_global_st *git__global_state(void)
libgit2/src/global.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,19 +8,24 @@ #define INCLUDE_global_h__ #include "mwindow.h"+#include "hash.h" -typedef struct {- struct {- char last[1024];- } error;+#if defined(GIT_THREADS) && defined(_MSC_VER)+# define GIT_MEMORY_BARRIER MemoryBarrier()+#elif defined(GIT_THREADS)+# define GIT_MEMORY_BARRIER __sync_synchronize()+#else+# define GIT_MEMORY_BARRIER /* noop */+#endif +typedef struct { git_error *last_error; git_error error_t;-- git_mwindow_ctl mem_ctl; } git_global_st; git_global_st *git__global_state(void);++extern git_mutex git__mwindow_mutex; #define GIT_GLOBAL (git__global_state())
+ libgit2/src/graph.c view
@@ -0,0 +1,178 @@++/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "revwalk.h"+#include "merge.h"+#include "git2/graph.h"++static int interesting(git_pqueue *list, git_commit_list *roots)+{+ unsigned int i;+ /* element 0 isn't used - we need to start at 1 */+ for (i = 1; i < list->size; i++) {+ git_commit_list_node *commit = list->d[i];+ if ((commit->flags & STALE) == 0)+ return 1;+ }++ while(roots) {+ if ((roots->item->flags & STALE) == 0)+ return 1;+ roots = roots->next;+ }++ return 0;+}++static int mark_parents(git_revwalk *walk, git_commit_list_node *one,+ git_commit_list_node *two)+{+ unsigned int i;+ git_commit_list *roots = NULL;+ git_pqueue list;++ /* if the commit is repeated, we have a our merge base already */+ if (one == two) {+ one->flags |= PARENT1 | PARENT2 | RESULT;+ return 0;+ }++ if (git_pqueue_init(&list, 2, git_commit_list_time_cmp) < 0)+ return -1;++ if (git_commit_list_parse(walk, one) < 0)+ goto on_error;+ one->flags |= PARENT1;+ if (git_pqueue_insert(&list, one) < 0)+ goto on_error;++ if (git_commit_list_parse(walk, two) < 0)+ goto on_error;+ two->flags |= PARENT2;+ if (git_pqueue_insert(&list, two) < 0)+ goto on_error;++ /* as long as there are non-STALE commits */+ while (interesting(&list, roots)) {+ git_commit_list_node *commit;+ int flags;++ commit = git_pqueue_pop(&list);+ if (commit == NULL)+ break;++ flags = commit->flags & (PARENT1 | PARENT2 | STALE);+ if (flags == (PARENT1 | PARENT2)) {+ if (!(commit->flags & RESULT))+ commit->flags |= RESULT;+ /* we mark the parents of a merge stale */+ flags |= STALE;+ }++ for (i = 0; i < commit->out_degree; i++) {+ git_commit_list_node *p = commit->parents[i];+ if ((p->flags & flags) == flags)+ continue;++ if (git_commit_list_parse(walk, p) < 0)+ goto on_error;++ p->flags |= flags;+ if (git_pqueue_insert(&list, p) < 0)+ goto on_error;+ }++ /* Keep track of root commits, to make sure the path gets marked */+ if (commit->out_degree == 0) {+ if (git_commit_list_insert(commit, &roots) == NULL)+ goto on_error;+ }+ }++ git_commit_list_free(&roots);+ git_pqueue_free(&list);+ return 0;++on_error:+ git_commit_list_free(&roots);+ git_pqueue_free(&list);+ return -1;+}+++static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two,+ size_t *ahead, size_t *behind)+{+ git_commit_list_node *commit;+ git_pqueue pq;+ int i;+ *ahead = 0;+ *behind = 0;++ if (git_pqueue_init(&pq, 2, git_commit_list_time_cmp) < 0)+ return -1;+ if (git_pqueue_insert(&pq, one) < 0)+ goto on_error;+ if (git_pqueue_insert(&pq, two) < 0)+ goto on_error;++ while ((commit = git_pqueue_pop(&pq)) != NULL) {+ if (commit->flags & RESULT ||+ (commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2))+ continue;+ else if (commit->flags & PARENT1)+ (*behind)++;+ else if (commit->flags & PARENT2)+ (*ahead)++;++ for (i = 0; i < commit->out_degree; i++) {+ git_commit_list_node *p = commit->parents[i];+ if (git_pqueue_insert(&pq, p) < 0)+ return -1;+ }+ commit->flags |= RESULT;+ }++ git_pqueue_free(&pq);+ return 0;++on_error:+ git_pqueue_free(&pq);+ return -1;+}++int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo,+ const git_oid *local, const git_oid *upstream)+{+ git_revwalk *walk;+ git_commit_list_node *commit_u, *commit_l;++ if (git_revwalk_new(&walk, repo) < 0)+ return -1;++ commit_u = git_revwalk__commit_lookup(walk, upstream);+ if (commit_u == NULL)+ goto on_error;++ commit_l = git_revwalk__commit_lookup(walk, local);+ if (commit_l == NULL)+ goto on_error;++ if (mark_parents(walk, commit_l, commit_u) < 0)+ goto on_error;+ if (ahead_behind(commit_l, commit_u, ahead, behind) < 0)+ goto on_error;++ git_revwalk_free(walk);++ return 0;++on_error:+ git_revwalk_free(walk);+ return -1;+}
libgit2/src/hash.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,67 +8,40 @@ #include "common.h" #include "hash.h" -#if defined(PPC_SHA1)-# include "ppc/sha1.h"-#else-# include "sha1.h"-#endif--struct git_hash_ctx {- SHA_CTX c;-};--git_hash_ctx *git_hash_new_ctx(void)+int git_hash_buf(git_oid *out, const void *data, size_t len) {- git_hash_ctx *ctx = git__malloc(sizeof(*ctx));-- if (!ctx)- return NULL;-- SHA1_Init(&ctx->c);+ git_hash_ctx ctx;+ int error = 0; - return ctx;-}+ if (git_hash_ctx_init(&ctx) < 0)+ return -1; -void git_hash_free_ctx(git_hash_ctx *ctx)-{- git__free(ctx);-}+ if ((error = git_hash_update(&ctx, data, len)) >= 0)+ error = git_hash_final(out, &ctx); -void git_hash_init(git_hash_ctx *ctx)-{- assert(ctx);- SHA1_Init(&ctx->c);+ git_hash_ctx_cleanup(&ctx);+ + return error; } -void git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n) {- assert(ctx);- SHA1_Update(&ctx->c, data, len);-}+ git_hash_ctx ctx;+ size_t i;+ int error = 0; -void git_hash_final(git_oid *out, git_hash_ctx *ctx)-{- assert(ctx);- SHA1_Final(out->id, &ctx->c);-}+ if (git_hash_ctx_init(&ctx) < 0)+ return -1; -void git_hash_buf(git_oid *out, const void *data, size_t len)-{- SHA_CTX c;+ for (i = 0; i < n; i++) {+ if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0)+ goto done;+ } - SHA1_Init(&c);- SHA1_Update(&c, data, len);- SHA1_Final(out->id, &c);-}+ error = git_hash_final(out, &ctx); -void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n)-{- SHA_CTX c;- size_t i;+done:+ git_hash_ctx_cleanup(&ctx); - SHA1_Init(&c);- for (i = 0; i < n; i++)- SHA1_Update(&c, vec[i].data, vec[i].len);- SHA1_Final(out->id, &c);+ return error; }
libgit2/src/hash.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -9,21 +9,33 @@ #include "git2/oid.h" +typedef struct git_hash_prov git_hash_prov; typedef struct git_hash_ctx git_hash_ctx; +int git_hash_global_init(void);+void git_hash_global_shutdown(void);++int git_hash_ctx_init(git_hash_ctx *ctx);+void git_hash_ctx_cleanup(git_hash_ctx *ctx);++#if defined(OPENSSL_SHA1)+# include "hash/hash_openssl.h"+#elif defined(WIN32_SHA1)+# include "hash/hash_win32.h"+#else+# include "hash/hash_generic.h"+#endif+ typedef struct { void *data; size_t len; } git_buf_vec; -git_hash_ctx *git_hash_new_ctx(void);-void git_hash_free_ctx(git_hash_ctx *ctx);--void git_hash_init(git_hash_ctx *c);-void git_hash_update(git_hash_ctx *c, const void *data, size_t len);-void git_hash_final(git_oid *out, git_hash_ctx *c);+int git_hash_init(git_hash_ctx *c);+int git_hash_update(git_hash_ctx *c, const void *data, size_t len);+int git_hash_final(git_oid *out, git_hash_ctx *c); -void git_hash_buf(git_oid *out, const void *data, size_t len);-void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);+int git_hash_buf(git_oid *out, const void *data, size_t len);+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n); #endif /* INCLUDE_hash_h__ */
+ libgit2/src/hash/hash_win32.c view
@@ -0,0 +1,291 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "common.h"+#include "global.h"+#include "hash.h"+#include "hash/hash_win32.h"++#include <wincrypt.h>+#include <strsafe.h>++static struct git_hash_prov hash_prov = {0};++/* Hash initialization */++/* Initialize CNG, if available */+GIT_INLINE(int) hash_cng_prov_init(void)+{+ OSVERSIONINFOEX version_test = {0};+ DWORD version_test_mask;+ DWORDLONG version_condition_mask = 0;+ char dll_path[MAX_PATH];+ DWORD dll_path_len, size_len;++ return -1;++ /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */+ version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);+ version_test.dwMajorVersion = 6;+ version_test.dwMinorVersion = 0;+ version_test.wServicePackMajor = 1;+ version_test.wServicePackMinor = 0;++ version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR);++ VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);+ VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);++ if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask))+ return -1;++ /* Load bcrypt.dll explicitly from the system directory */+ if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH ||+ StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||+ StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||+ (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL)+ return -1;++ /* Load the function addresses */+ if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL ||+ (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL ||+ (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL ||+ (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL ||+ (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL ||+ (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL ||+ (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) {+ FreeLibrary(hash_prov.prov.cng.dll);+ return -1;+ }++ /* Load the SHA1 algorithm */+ if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) {+ FreeLibrary(hash_prov.prov.cng.dll);+ return -1;+ }++ /* Get storage space for the hash object */+ if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) {+ hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);+ FreeLibrary(hash_prov.prov.cng.dll);+ return -1;+ }++ hash_prov.type = CNG;+ return 0;+}++GIT_INLINE(void) hash_cng_prov_shutdown(void)+{+ hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);+ FreeLibrary(hash_prov.prov.cng.dll);++ hash_prov.type = INVALID;+}++/* Initialize CryptoAPI */+GIT_INLINE(int) hash_cryptoapi_prov_init()+{+ if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))+ return -1;++ hash_prov.type = CRYPTOAPI;+ return 0;+}++GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void)+{+ CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0);++ hash_prov.type = INVALID;+}++int git_hash_global_init()+{+ int error = 0;++ if (hash_prov.type != INVALID)+ return 0;++ if ((error = hash_cng_prov_init()) < 0)+ error = hash_cryptoapi_prov_init();++ return error; +}++void git_hash_global_shutdown()+{+ if (hash_prov.type == CNG)+ hash_cng_prov_shutdown();+ else if(hash_prov.type == CRYPTOAPI)+ hash_cryptoapi_prov_shutdown();+}++/* CryptoAPI: available in Windows XP and newer */++GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_ctx *ctx)+{+ ctx->type = CRYPTOAPI;+ ctx->prov = &hash_prov;++ return git_hash_init(ctx);+}++GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx)+{+ if (ctx->ctx.cryptoapi.valid)+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);++ if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {+ ctx->ctx.cryptoapi.valid = 0;+ return -1;+ }++ ctx->ctx.cryptoapi.valid = 1;+ return 0;+}++GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *data, size_t len)+{+ assert(ctx->ctx.cryptoapi.valid);++ if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, (const BYTE *)data, (DWORD)len, 0))+ return -1;++ return 0;+}++GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx)+{+ DWORD len = 20;+ int error = 0;++ assert(ctx->ctx.cryptoapi.valid);++ if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0))+ error = -1;++ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);+ ctx->ctx.cryptoapi.valid = 0;++ return error;+}++GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx)+{+ if (ctx->ctx.cryptoapi.valid)+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);+}++/* CNG: Available in Windows Server 2008 and newer */++GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx)+{+ if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL)+ return -1;++ if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) {+ git__free(ctx->ctx.cng.hash_object);+ return -1;+ }++ ctx->type = CNG;+ ctx->prov = &hash_prov;++ return 0;+}++GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx)+{+ BYTE hash[GIT_OID_RAWSZ];++ if (!ctx->ctx.cng.updated)+ return 0;++ /* CNG needs to be finished to restart */+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0)+ return -1;++ ctx->ctx.cng.updated = 0;++ return 0;+}++GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *data, size_t len)+{+ if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, (PBYTE)data, (ULONG)len, 0) < 0)+ return -1;++ return 0;+}++GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx)+{+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0)+ return -1;++ ctx->ctx.cng.updated = 0;++ return 0;+}++GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx)+{+ ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle);+ git__free(ctx->ctx.cng.hash_object);+}++/* Indirection between CryptoAPI and CNG */++int git_hash_ctx_init(git_hash_ctx *ctx)+{+ int error = 0;++ assert(ctx);++ /*+ * When compiled with GIT_THREADS, the global hash_prov data is+ * initialized with git_threads_init. Otherwise, it must be initialized+ * at first use.+ */+ if (hash_prov.type == INVALID && (error = git_hash_global_init()) < 0)+ return error;++ memset(ctx, 0x0, sizeof(git_hash_ctx));++ return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx);+}++int git_hash_init(git_hash_ctx *ctx)+{+ assert(ctx && ctx->type);+ return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);+}++int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)+{+ assert(ctx && ctx->type);+ return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);+}++int git_hash_final(git_oid *out, git_hash_ctx *ctx)+{+ assert(ctx && ctx->type);+ return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);+}++void git_hash_ctx_cleanup(git_hash_ctx *ctx)+{+ assert(ctx);++ if (ctx->type == CNG)+ hash_ctx_cng_cleanup(ctx);+ else if(ctx->type == CRYPTOAPI)+ hash_ctx_cryptoapi_cleanup(ctx);+}
+ libgit2/src/hashsig.c view
@@ -0,0 +1,368 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#include "hashsig.h"+#include "fileops.h"+#include "util.h"++typedef uint32_t hashsig_t;+typedef uint64_t hashsig_state;++#define HASHSIG_SCALE 100++#define HASHSIG_HASH_WINDOW 32+#define HASHSIG_HASH_START 0+#define HASHSIG_HASH_SHIFT 5+#define HASHSIG_HASH_MASK 0x7FFFFFFF++#define HASHSIG_HEAP_SIZE ((1 << 7) - 1)++typedef int (*hashsig_cmp)(const void *a, const void *b, void *);++typedef struct {+ int size, asize;+ hashsig_cmp cmp;+ hashsig_t values[HASHSIG_HEAP_SIZE];+} hashsig_heap;++typedef struct {+ hashsig_state state, shift_n;+ char window[HASHSIG_HASH_WINDOW];+ int win_len, win_pos, saw_lf;+} hashsig_in_progress;++#define HASHSIG_IN_PROGRESS_INIT { HASHSIG_HASH_START, 1, {0}, 0, 0, 1 }++struct git_hashsig {+ hashsig_heap mins;+ hashsig_heap maxs;+ git_hashsig_option_t opt;+ int considered;+};++#define HEAP_LCHILD_OF(I) (((I)*2)+1)+#define HEAP_RCHILD_OF(I) (((I)*2)+2)+#define HEAP_PARENT_OF(I) (((I)-1)>>1)++static void hashsig_heap_init(hashsig_heap *h, hashsig_cmp cmp)+{+ h->size = 0;+ h->asize = HASHSIG_HEAP_SIZE;+ h->cmp = cmp;+}++static int hashsig_cmp_max(const void *a, const void *b, void *payload)+{+ hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;+ GIT_UNUSED(payload);+ return (av < bv) ? -1 : (av > bv) ? 1 : 0;+}++static int hashsig_cmp_min(const void *a, const void *b, void *payload)+{+ hashsig_t av = *(const hashsig_t *)a, bv = *(const hashsig_t *)b;+ GIT_UNUSED(payload);+ return (av > bv) ? -1 : (av < bv) ? 1 : 0;+}++static void hashsig_heap_up(hashsig_heap *h, int el)+{+ int parent_el = HEAP_PARENT_OF(el);++ while (el > 0 && h->cmp(&h->values[parent_el], &h->values[el], NULL) > 0) {+ hashsig_t t = h->values[el];+ h->values[el] = h->values[parent_el];+ h->values[parent_el] = t;++ el = parent_el;+ parent_el = HEAP_PARENT_OF(el);+ }+}++static void hashsig_heap_down(hashsig_heap *h, int el)+{+ hashsig_t v, lv, rv;++ /* 'el < h->size / 2' tests if el is bottom row of heap */++ while (el < h->size / 2) {+ int lel = HEAP_LCHILD_OF(el), rel = HEAP_RCHILD_OF(el), swapel;++ v = h->values[el];+ lv = h->values[lel];+ rv = h->values[rel];++ if (h->cmp(&v, &lv, NULL) < 0 && h->cmp(&v, &rv, NULL) < 0)+ break;++ swapel = (h->cmp(&lv, &rv, NULL) < 0) ? lel : rel;++ h->values[el] = h->values[swapel];+ h->values[swapel] = v;++ el = swapel;+ }+}++static void hashsig_heap_sort(hashsig_heap *h)+{+ /* only need to do this at the end for signature comparison */+ git__qsort_r(h->values, h->size, sizeof(hashsig_t), h->cmp, NULL);+}++static void hashsig_heap_insert(hashsig_heap *h, hashsig_t val)+{+ /* if heap is full, pop top if new element should replace it */+ if (h->size == h->asize && h->cmp(&val, &h->values[0], NULL) > 0) {+ h->size--;+ h->values[0] = h->values[h->size];+ hashsig_heap_down(h, 0);+ }++ /* if heap is not full, insert new element */+ if (h->size < h->asize) {+ h->values[h->size++] = val;+ hashsig_heap_up(h, h->size - 1);+ }+}++GIT_INLINE(bool) hashsig_include_char(+ char ch, git_hashsig_option_t opt, int *saw_lf)+{+ if ((opt & GIT_HASHSIG_IGNORE_WHITESPACE) && git__isspace(ch))+ return false;++ if (opt & GIT_HASHSIG_SMART_WHITESPACE) {+ if (ch == '\r' || (*saw_lf && git__isspace(ch)))+ return false;++ *saw_lf = (ch == '\n');+ }++ return true;+}++static void hashsig_initial_window(+ git_hashsig *sig,+ const char **data,+ size_t size,+ hashsig_in_progress *prog)+{+ hashsig_state state, shift_n;+ int win_len;+ const char *scan, *end;++ /* init until we have processed at least HASHSIG_HASH_WINDOW data */++ if (prog->win_len >= HASHSIG_HASH_WINDOW)+ return;++ state = prog->state;+ win_len = prog->win_len;+ shift_n = prog->shift_n;++ scan = *data;+ end = scan + size;++ while (scan < end && win_len < HASHSIG_HASH_WINDOW) {+ char ch = *scan++;++ if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf))+ continue;++ state = (state * HASHSIG_HASH_SHIFT + ch) & HASHSIG_HASH_MASK;++ if (!win_len)+ shift_n = 1;+ else+ shift_n = (shift_n * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK;++ prog->window[win_len++] = ch;+ }++ /* insert initial hash if we just finished */++ if (win_len == HASHSIG_HASH_WINDOW) {+ hashsig_heap_insert(&sig->mins, (hashsig_t)state);+ hashsig_heap_insert(&sig->maxs, (hashsig_t)state);+ sig->considered = 1;+ }++ prog->state = state;+ prog->win_len = win_len;+ prog->shift_n = shift_n;++ *data = scan;+}++static int hashsig_add_hashes(+ git_hashsig *sig,+ const char *data,+ size_t size,+ hashsig_in_progress *prog)+{+ const char *scan = data, *end = data + size;+ hashsig_state state, shift_n, rmv;++ if (prog->win_len < HASHSIG_HASH_WINDOW)+ hashsig_initial_window(sig, &scan, size, prog);++ state = prog->state;+ shift_n = prog->shift_n;++ /* advance window, adding new chars and removing old */++ for (; scan < end; ++scan) {+ char ch = *scan;++ if (!hashsig_include_char(ch, sig->opt, &prog->saw_lf))+ continue;++ rmv = shift_n * prog->window[prog->win_pos];++ state = (state - rmv) & HASHSIG_HASH_MASK;+ state = (state * HASHSIG_HASH_SHIFT) & HASHSIG_HASH_MASK;+ state = (state + ch) & HASHSIG_HASH_MASK;++ hashsig_heap_insert(&sig->mins, (hashsig_t)state);+ hashsig_heap_insert(&sig->maxs, (hashsig_t)state);+ sig->considered++;++ prog->window[prog->win_pos] = ch;+ prog->win_pos = (prog->win_pos + 1) % HASHSIG_HASH_WINDOW;+ }++ prog->state = state;++ return 0;+}++static int hashsig_finalize_hashes(git_hashsig *sig)+{+ if (sig->mins.size < HASHSIG_HEAP_SIZE) {+ giterr_set(GITERR_INVALID,+ "File too small for similarity signature calculation");+ return GIT_EBUFS;+ }++ hashsig_heap_sort(&sig->mins);+ hashsig_heap_sort(&sig->maxs);++ return 0;+}++static git_hashsig *hashsig_alloc(git_hashsig_option_t opts)+{+ git_hashsig *sig = git__calloc(1, sizeof(git_hashsig));+ if (!sig)+ return NULL;++ hashsig_heap_init(&sig->mins, hashsig_cmp_min);+ hashsig_heap_init(&sig->maxs, hashsig_cmp_max);+ sig->opt = opts;++ return sig;+}++int git_hashsig_create(+ git_hashsig **out,+ const char *buf,+ size_t buflen,+ git_hashsig_option_t opts)+{+ int error;+ hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT;+ git_hashsig *sig = hashsig_alloc(opts);+ GITERR_CHECK_ALLOC(sig);++ error = hashsig_add_hashes(sig, buf, buflen, &prog);++ if (!error)+ error = hashsig_finalize_hashes(sig);++ if (!error)+ *out = sig;+ else+ git_hashsig_free(sig);++ return error;+}++int git_hashsig_create_fromfile(+ git_hashsig **out,+ const char *path,+ git_hashsig_option_t opts)+{+ char buf[4096];+ ssize_t buflen = 0;+ int error = 0, fd;+ hashsig_in_progress prog = HASHSIG_IN_PROGRESS_INIT;+ git_hashsig *sig = hashsig_alloc(opts);+ GITERR_CHECK_ALLOC(sig);++ if ((fd = git_futils_open_ro(path)) < 0) {+ git__free(sig);+ return fd;+ }++ while (!error) {+ if ((buflen = p_read(fd, buf, sizeof(buf))) <= 0) {+ if ((error = (int)buflen) < 0)+ giterr_set(GITERR_OS,+ "Read error on '%s' calculating similarity hashes", path);+ break;+ }++ error = hashsig_add_hashes(sig, buf, buflen, &prog);+ }++ p_close(fd);++ if (!error)+ error = hashsig_finalize_hashes(sig);++ if (!error)+ *out = sig;+ else+ git_hashsig_free(sig);++ return error;+}++void git_hashsig_free(git_hashsig *sig)+{+ git__free(sig);+}++static int hashsig_heap_compare(const hashsig_heap *a, const hashsig_heap *b)+{+ int matches = 0, i, j, cmp;++ assert(a->cmp == b->cmp);++ /* hash heaps are sorted - just look for overlap vs total */++ for (i = 0, j = 0; i < a->size && j < b->size; ) {+ cmp = a->cmp(&a->values[i], &b->values[j], NULL);++ if (cmp < 0)+ ++i;+ else if (cmp > 0)+ ++j;+ else {+ ++i; ++j; ++matches;+ }+ }++ return HASHSIG_SCALE * (matches * 2) / (a->size + b->size);+}++int git_hashsig_compare(const git_hashsig *a, const git_hashsig *b)+{+ return (hashsig_heap_compare(&a->mins, &b->mins) ++ hashsig_heap_compare(&a->maxs, &b->maxs)) / 2;+}+
+ libgit2/src/hashsig.h view
@@ -0,0 +1,72 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_hashsig_h__+#define INCLUDE_hashsig_h__++#include "common.h"++/**+ * Similarity signature of line hashes for a buffer+ */+typedef struct git_hashsig git_hashsig;++typedef enum {+ GIT_HASHSIG_NORMAL = 0, /* use all data */+ GIT_HASHSIG_IGNORE_WHITESPACE = 1, /* ignore whitespace */+ GIT_HASHSIG_SMART_WHITESPACE = 2, /* ignore \r and all space after \n */+} git_hashsig_option_t;++/**+ * Build a similarity signature for a buffer+ *+ * If you have passed a whitespace-ignoring buffer, then the whitespace+ * will be removed from the buffer while it is being processed, modifying+ * the buffer in place. Sorry about that!+ *+ * This will return an error if the buffer doesn't contain enough data to+ * compute a valid signature.+ *+ * @param out The array of hashed runs representing the file content+ * @param buf The contents of the file to hash+ * @param buflen The length of the data at `buf`+ * @param generate_pairwise_hashes Should pairwise runs be hashed+ */+extern int git_hashsig_create(+ git_hashsig **out,+ const char *buf,+ size_t buflen,+ git_hashsig_option_t opts);++/**+ * Build a similarity signature from a file+ *+ * This walks through the file, only loading a maximum of 4K of file data at+ * a time. Otherwise, it acts just like `git_hashsig_create`.+ *+ * This will return an error if the file doesn't contain enough data to+ * compute a valid signature.+ */+extern int git_hashsig_create_fromfile(+ git_hashsig **out,+ const char *path,+ git_hashsig_option_t opts);++/**+ * Release memory for a content similarity signature+ */+extern void git_hashsig_free(git_hashsig *sig);++/**+ * Measure similarity between two files+ *+ * @return <0 for error, [0 to 100] as similarity score+ */+extern int git_hashsig_compare(+ const git_hashsig *a,+ const git_hashsig *b);++#endif
libgit2/src/ignore.c view
@@ -1,20 +1,39 @@+#include "git2/ignore.h" #include "ignore.h"+#include "attr.h" #include "path.h"+#include "config.h" #define GIT_IGNORE_INTERNAL "[internal]exclude"-#define GIT_IGNORE_FILE_INREPO "info/exclude"-#define GIT_IGNORE_FILE ".gitignore" +#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"+ static int parse_ignore_file(- git_repository *repo, const char *buffer, git_attr_file *ignores)+ git_repository *repo, void *parsedata, const char *buffer, git_attr_file *ignores) { int error = 0; git_attr_fnmatch *match = NULL; const char *scan = NULL; char *context = NULL;+ bool ignore_case = false;+ git_config *cfg = NULL;+ int val; - GIT_UNUSED(repo);+ /* Prefer to have the caller pass in a git_ignores as the parsedata object.+ * If they did not, then we can (much more slowly) find the value of+ * ignore_case by using the repository object. */+ if (parsedata != NULL) {+ ignore_case = ((git_ignores *)parsedata)->ignore_case;+ } else {+ if ((error = git_repository_config(&cfg, repo)) < 0)+ return error; + if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)+ ignore_case = (val != 0);++ git_config_free(cfg);+ }+ if (ignores->key && git__suffixcmp(ignores->key, "/" GIT_IGNORE_FILE) == 0) { context = ignores->key + 2; context[strlen(context) - strlen(GIT_IGNORE_FILE)] = '\0';@@ -28,10 +47,16 @@ GITERR_CHECK_ALLOC(match); } + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;+ if (!(error = git_attr_fnmatch__parse( match, ignores->pool, context, &scan))) {- match->flags = match->flags | GIT_ATTR_FNMATCH_IGNORE;+ match->flags |= GIT_ATTR_FNMATCH_IGNORE;++ if (ignore_case)+ match->flags |= GIT_ATTR_FNMATCH_ICASE;+ scan = git__next_line(scan); error = git_vector_insert(&ignores->rules, match); }@@ -55,15 +80,28 @@ return error; } -#define push_ignore_file(R,S,B,F) \- git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(S))+#define push_ignore_file(R,IGN,S,B,F) \+ git_attr_cache__push_file((R),(B),(F),GIT_ATTR_FILE_FROM_FILE,parse_ignore_file,(IGN),(S)) static int push_one_ignore(void *ref, git_buf *path) { git_ignores *ign = (git_ignores *)ref;- return push_ignore_file(ign->repo, &ign->ign_path, path->ptr, GIT_IGNORE_FILE);+ return push_ignore_file(ign->repo, ign, &ign->ign_path, path->ptr, GIT_IGNORE_FILE); } +static int get_internal_ignores(git_attr_file **ign, git_repository *repo)+{+ int error;++ if (!(error = git_attr_cache__init(repo)))+ error = git_attr_cache__internal_file(repo, GIT_IGNORE_INTERNAL, ign);++ if (!error && !(*ign)->rules.length)+ error = parse_ignore_file(repo, NULL, GIT_IGNORE_DEFAULT_RULES, *ign);++ return error;+}+ int git_ignore__for_path( git_repository *repo, const char *path,@@ -71,6 +109,8 @@ { int error = 0; const char *workdir = git_repository_workdir(repo);+ git_config *cfg = NULL;+ int val; assert(ignores); @@ -78,6 +118,17 @@ git_buf_init(&ignores->dir, 0); ignores->ign_internal = NULL; + /* Set the ignore_case flag appropriately */+ if ((error = git_repository_config(&cfg, repo)) < 0)+ goto cleanup;++ if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)+ ignores->ignore_case = (val != 0);+ else+ ignores->ignore_case = 0;++ git_config_free(cfg);+ if ((error = git_vector_init(&ignores->ign_path, 8, NULL)) < 0 || (error = git_vector_init(&ignores->ign_global, 2, NULL)) < 0 || (error = git_attr_cache__init(repo)) < 0)@@ -92,8 +143,7 @@ goto cleanup; /* set up internals */- error = git_attr_cache__internal_file(- repo, GIT_IGNORE_INTERNAL, &ignores->ign_internal);+ error = get_internal_ignores(&ignores->ign_internal, repo); if (error < 0) goto cleanup; @@ -106,14 +156,14 @@ } /* load .git/info/exclude */- error = push_ignore_file(repo, &ignores->ign_global,+ error = push_ignore_file(repo, ignores, &ignores->ign_global, git_repository_path(repo), GIT_IGNORE_FILE_INREPO); if (error < 0) goto cleanup; /* load core.excludesfile */ if (git_repository_attr_cache(repo)->cfg_excl_file != NULL)- error = push_ignore_file(repo, &ignores->ign_global, NULL,+ error = push_ignore_file(repo, ignores, &ignores->ign_global, NULL, git_repository_attr_cache(repo)->cfg_excl_file); cleanup:@@ -129,7 +179,7 @@ return -1; else return push_ignore_file(- ign->repo, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE);+ ign->repo, ign, &ign->ign_path, ign->dir.ptr, GIT_IGNORE_FILE); } int git_ignore__pop_dir(git_ignores *ign)@@ -154,7 +204,7 @@ static bool ignore_lookup_in_rules( git_vector *rules, git_attr_path *path, int *ignored) {- unsigned int j;+ size_t j; git_attr_fnmatch *match; git_vector_rforeach(rules, j, match) {@@ -201,3 +251,110 @@ git_attr_path__free(&path); return 0; }++int git_ignore_add_rule(+ git_repository *repo,+ const char *rules)+{+ int error;+ git_attr_file *ign_internal;++ if (!(error = get_internal_ignores(&ign_internal, repo)))+ error = parse_ignore_file(repo, NULL, rules, ign_internal);++ return error;+}++int git_ignore_clear_internal_rules(+ git_repository *repo)+{+ int error;+ git_attr_file *ign_internal;++ if (!(error = get_internal_ignores(&ign_internal, repo))) {+ git_attr_file__clear_rules(ign_internal);++ return parse_ignore_file(+ repo, NULL, GIT_IGNORE_DEFAULT_RULES, ign_internal);+ }++ return error;+}++int git_ignore_path_is_ignored(+ int *ignored,+ git_repository *repo,+ const char *pathname)+{+ int error;+ const char *workdir;+ git_attr_path path;+ char *tail, *end;+ bool full_is_dir;+ git_ignores ignores;+ unsigned int i;+ git_attr_file *file;++ assert(ignored && pathname);++ workdir = repo ? git_repository_workdir(repo) : NULL;++ if ((error = git_attr_path__init(&path, pathname, workdir)) < 0)+ return error;++ tail = path.path;+ end = &path.full.ptr[path.full.size];+ full_is_dir = path.is_dir;++ while (1) {+ /* advance to next component of path */+ path.basename = tail;++ while (tail < end && *tail != '/') tail++;+ *tail = '\0';++ path.full.size = (tail - path.full.ptr);+ path.is_dir = (tail == end) ? full_is_dir : true;++ /* update ignores for new path fragment */+ if (path.basename == path.path)+ error = git_ignore__for_path(repo, path.path, &ignores);+ else+ error = git_ignore__push_dir(&ignores, path.basename);+ if (error < 0)+ break;++ /* first process builtins - success means path was found */+ if (ignore_lookup_in_rules(+ &ignores.ign_internal->rules, &path, ignored))+ goto cleanup;++ /* next process files in the path */+ git_vector_foreach(&ignores.ign_path, i, file) {+ if (ignore_lookup_in_rules(&file->rules, &path, ignored))+ goto cleanup;+ }++ /* last process global ignores */+ git_vector_foreach(&ignores.ign_global, i, file) {+ if (ignore_lookup_in_rules(&file->rules, &path, ignored))+ goto cleanup;+ }++ /* if we found no rules before reaching the end, we're done */+ if (tail == end)+ break;++ /* reinstate divider in path */+ *tail = '/';+ while (*tail == '/') tail++;+ }++ *ignored = 0;++cleanup:+ git_attr_path__free(&path);+ git_ignore__free(&ignores);+ return error;+}+
libgit2/src/ignore.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -9,7 +9,12 @@ #include "repository.h" #include "vector.h"+#include "attr_file.h" +#define GIT_IGNORE_FILE ".gitignore"+#define GIT_IGNORE_FILE_INREPO "info/exclude"+#define GIT_IGNORE_FILE_XDG "ignore"+ /* The git_ignores structure maintains three sets of ignores: * - internal ignores * - per directory ignores@@ -23,6 +28,7 @@ git_attr_file *ign_internal; git_vector ign_path; git_vector ign_global;+ unsigned int ignore_case:1; } git_ignores; extern int git_ignore__for_path(git_repository *repo, const char *path, git_ignores *ign);
libgit2/src/index.c view
@@ -1,969 +1,1681 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#include <stddef.h>--#include "common.h"-#include "repository.h"-#include "index.h"-#include "tree.h"-#include "tree-cache.h"-#include "hash.h"-#include "git2/odb.h"-#include "git2/blob.h"--#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7)-#define short_entry_size(len) entry_size(struct entry_short, len)-#define long_entry_size(len) entry_size(struct entry_long, len)--#define minimal_entry_size (offsetof(struct entry_short, path))--static const size_t INDEX_FOOTER_SIZE = GIT_OID_RAWSZ;-static const size_t INDEX_HEADER_SIZE = 12;--static const unsigned int INDEX_VERSION_NUMBER = 2;-static const unsigned int INDEX_VERSION_NUMBER_EXT = 3;--static const unsigned int INDEX_HEADER_SIG = 0x44495243;-static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'};-static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'};--#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx)))--struct index_header {- uint32_t signature;- uint32_t version;- uint32_t entry_count;-};--struct index_extension {- char signature[4];- uint32_t extension_size;-};--struct entry_time {- uint32_t seconds;- uint32_t nanoseconds;-};--struct entry_short {- struct entry_time ctime;- struct entry_time mtime;- uint32_t dev;- uint32_t ino;- uint32_t mode;- uint32_t uid;- uint32_t gid;- uint32_t file_size;- git_oid oid;- uint16_t flags;- char path[1]; /* arbitrary length */-};--struct entry_long {- struct entry_time ctime;- struct entry_time mtime;- uint32_t dev;- uint32_t ino;- uint32_t mode;- uint32_t uid;- uint32_t gid;- uint32_t file_size;- git_oid oid;- uint16_t flags;- uint16_t flags_extended;- char path[1]; /* arbitrary length */-};--/* local declarations */-static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size);-static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size);-static int read_header(struct index_header *dest, const void *buffer);--static int parse_index(git_index *index, const char *buffer, size_t buffer_size);-static int is_index_extended(git_index *index);-static int write_index(git_index *index, git_filebuf *file);--static void index_entry_free(git_index_entry *entry);--static int index_srch(const void *key, const void *array_member)-{- const git_index_entry *entry = array_member;-- return strcmp(key, entry->path);-}--static int index_cmp(const void *a, const void *b)-{- const git_index_entry *entry_a = a;- const git_index_entry *entry_b = b;-- return strcmp(entry_a->path, entry_b->path);-}--static int unmerged_srch(const void *key, const void *array_member)-{- const git_index_entry_unmerged *entry = array_member;-- return strcmp(key, entry->path);-}--static int unmerged_cmp(const void *a, const void *b)-{- const git_index_entry_unmerged *info_a = a;- const git_index_entry_unmerged *info_b = b;-- return strcmp(info_a->path, info_b->path);-}--static unsigned int index_create_mode(unsigned int mode)-{- if (S_ISLNK(mode))- return S_IFLNK;- if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR))- return (S_IFLNK | S_IFDIR);- return S_IFREG | ((mode & 0100) ? 0755 : 0644);-}--int git_index_open(git_index **index_out, const char *index_path)-{- git_index *index;-- assert(index_out && index_path);-- index = git__calloc(1, sizeof(git_index));- GITERR_CHECK_ALLOC(index);-- index->index_file_path = git__strdup(index_path);- GITERR_CHECK_ALLOC(index->index_file_path);-- if (git_vector_init(&index->entries, 32, index_cmp) < 0)- return -1;-- /* Check if index file is stored on disk already */- if (git_path_exists(index->index_file_path) == true)- index->on_disk = 1;-- *index_out = index;- GIT_REFCOUNT_INC(index);- return git_index_read(index);-}--static void index_free(git_index *index)-{- git_index_entry *e;- unsigned int i;-- git_index_clear(index);- git_vector_foreach(&index->entries, i, e) {- index_entry_free(e);- }- git_vector_free(&index->entries);- git_vector_foreach(&index->unmerged, i, e) {- index_entry_free(e);- }- git_vector_free(&index->unmerged);-- git__free(index->index_file_path);- git__free(index);-}--void git_index_free(git_index *index)-{- if (index == NULL)- return;-- GIT_REFCOUNT_DEC(index, index_free);-}--void git_index_clear(git_index *index)-{- unsigned int i;-- assert(index);-- for (i = 0; i < index->entries.length; ++i) {- git_index_entry *e;- e = git_vector_get(&index->entries, i);- git__free(e->path);- git__free(e);- }-- for (i = 0; i < index->unmerged.length; ++i) {- git_index_entry_unmerged *e;- e = git_vector_get(&index->unmerged, i);- git__free(e->path);- git__free(e);- }-- git_vector_clear(&index->entries);- git_vector_clear(&index->unmerged);- index->last_modified = 0;-- git_tree_cache_free(index->tree);- index->tree = NULL;-}--int git_index_read(git_index *index)-{- int error, updated;- git_buf buffer = GIT_BUF_INIT;- time_t mtime;-- assert(index->index_file_path);-- if (!index->on_disk || git_path_exists(index->index_file_path) == false) {- git_index_clear(index);- index->on_disk = 0;- return 0;- }-- /* We don't want to update the mtime if we fail to parse the index */- mtime = index->last_modified;- error = git_futils_readbuffer_updated(- &buffer, index->index_file_path, &mtime, &updated);- if (error < 0)- return error;-- if (updated) {- git_index_clear(index);- error = parse_index(index, buffer.ptr, buffer.size);-- if (!error)- index->last_modified = mtime;-- git_buf_free(&buffer);- }-- return error;-}--int git_index_write(git_index *index)-{- git_filebuf file = GIT_FILEBUF_INIT;- struct stat indexst;- int error;-- git_vector_sort(&index->entries);-- if ((error = git_filebuf_open(- &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0)- return error;-- if ((error = write_index(index, &file)) < 0) {- git_filebuf_cleanup(&file);- return error;- }-- if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0)- return error;-- if (p_stat(index->index_file_path, &indexst) == 0) {- index->last_modified = indexst.st_mtime;- index->on_disk = 1;- }-- return 0;-}--unsigned int git_index_entrycount(git_index *index)-{- assert(index);- return index->entries.length;-}--unsigned int git_index_entrycount_unmerged(git_index *index)-{- assert(index);- return index->unmerged.length;-}--git_index_entry *git_index_get(git_index *index, unsigned int n)-{- git_vector_sort(&index->entries);- return git_vector_get(&index->entries, n);-}--void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry)-{- entry->ctime.seconds = (git_time_t)st->st_ctime;- entry->mtime.seconds = (git_time_t)st->st_mtime;- /* entry->mtime.nanoseconds = st->st_mtimensec; */- /* entry->ctime.nanoseconds = st->st_ctimensec; */- entry->dev = st->st_rdev;- entry->ino = st->st_ino;- entry->mode = index_create_mode(st->st_mode);- entry->uid = st->st_uid;- entry->gid = st->st_gid;- entry->file_size = st->st_size;-}--static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage)-{- git_index_entry *entry = NULL;- struct stat st;- git_oid oid;- const char *workdir;- git_buf full_path = GIT_BUF_INIT;- int error;-- assert(stage >= 0 && stage <= 3);-- if (INDEX_OWNER(index) == NULL ||- (workdir = git_repository_workdir(INDEX_OWNER(index))) == NULL)- {- giterr_set(GITERR_INDEX,- "Could not initialize index entry. Repository is bare");- return -1;- }-- if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0)- return error;-- if ((error = git_path_lstat(full_path.ptr, &st)) < 0) {- git_buf_free(&full_path);- return error;- }-- git_buf_free(&full_path); /* done with full path */-- /* There is no need to validate the rel_path here, since it will be- * immediately validated by the call to git_blob_create_fromfile.- */-- /* write the blob to disk and get the oid */- if ((error = git_blob_create_fromfile(&oid, INDEX_OWNER(index), rel_path)) < 0)- return error;-- entry = git__calloc(1, sizeof(git_index_entry));- GITERR_CHECK_ALLOC(entry);-- git_index__init_entry_from_stat(&st, entry);-- entry->oid = oid;- entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT);- entry->path = git__strdup(rel_path);- GITERR_CHECK_ALLOC(entry->path);-- *entry_out = entry;- return 0;-}--static git_index_entry *index_entry_dup(const git_index_entry *source_entry)-{- git_index_entry *entry;-- entry = git__malloc(sizeof(git_index_entry));- if (!entry)- return NULL;-- memcpy(entry, source_entry, sizeof(git_index_entry));-- /* duplicate the path string so we own it */- entry->path = git__strdup(entry->path);- if (!entry->path)- return NULL;-- return entry;-}--static void index_entry_free(git_index_entry *entry)-{- if (!entry)- return;- git__free(entry->path);- git__free(entry);-}--static int index_insert(git_index *index, git_index_entry *entry, int replace)-{- size_t path_length;- int position;- git_index_entry **entry_array;-- assert(index && entry && entry->path != NULL);-- /* make sure that the path length flag is correct */- path_length = strlen(entry->path);-- entry->flags &= ~GIT_IDXENTRY_NAMEMASK;-- if (path_length < GIT_IDXENTRY_NAMEMASK)- entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK;- else- entry->flags |= GIT_IDXENTRY_NAMEMASK;;-- /*- * replacing is not requested: just insert entry at the end;- * the index is no longer sorted- */- if (!replace)- return git_vector_insert(&index->entries, entry);-- /* look if an entry with this path already exists */- position = git_index_find(index, entry->path);-- /*- * if no entry exists add the entry at the end;- * the index is no longer sorted- */- if (position == GIT_ENOTFOUND)- return git_vector_insert(&index->entries, entry);-- /* exists, replace it */- entry_array = (git_index_entry **) index->entries.contents;- git__free(entry_array[position]->path);- git__free(entry_array[position]);- entry_array[position] = entry;-- return 0;-}--static int index_add(git_index *index, const char *path, int stage, int replace)-{- git_index_entry *entry = NULL;- int ret;-- if ((ret = index_entry_init(&entry, index, path, stage)) < 0 ||- (ret = index_insert(index, entry, replace)) < 0)- {- index_entry_free(entry);- return ret;- }-- git_tree_cache_invalidate_path(index->tree, entry->path);- return 0;-}--int git_index_add(git_index *index, const char *path, int stage)-{- return index_add(index, path, stage, 1);-}--int git_index_append(git_index *index, const char *path, int stage)-{- return index_add(index, path, stage, 0);-}--static int index_add2(- git_index *index, const git_index_entry *source_entry, int replace)-{- git_index_entry *entry = NULL;- int ret;-- entry = index_entry_dup(source_entry);- if (entry == NULL)- return -1;-- if ((ret = index_insert(index, entry, replace)) < 0) {- index_entry_free(entry);- return ret;- }-- git_tree_cache_invalidate_path(index->tree, entry->path);- return 0;-}--int git_index_add2(git_index *index, const git_index_entry *source_entry)-{- return index_add2(index, source_entry, 1);-}--int git_index_append2(git_index *index, const git_index_entry *source_entry)-{- return index_add2(index, source_entry, 1);-}--int git_index_remove(git_index *index, int position)-{- int error;- git_index_entry *entry;-- git_vector_sort(&index->entries);-- entry = git_vector_get(&index->entries, position);- if (entry != NULL)- git_tree_cache_invalidate_path(index->tree, entry->path);-- error = git_vector_remove(&index->entries, (unsigned int)position);-- if (!error)- index_entry_free(entry);-- return error;-}--int git_index_find(git_index *index, const char *path)-{- return git_vector_bsearch2(&index->entries, index_srch, path);-}--unsigned int git_index__prefix_position(git_index *index, const char *path)-{- unsigned int pos;-- git_vector_bsearch3(&pos, &index->entries, index_srch, path);-- return pos;-}--void git_index_uniq(git_index *index)-{- git_vector_uniq(&index->entries);-}--const git_index_entry_unmerged *git_index_get_unmerged_bypath(- git_index *index, const char *path)-{- int pos;- assert(index && path);-- if (!index->unmerged.length)- return NULL;-- if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < 0)- return NULL;-- return git_vector_get(&index->unmerged, pos);-}--const git_index_entry_unmerged *git_index_get_unmerged_byindex(- git_index *index, unsigned int n)-{- assert(index);- return git_vector_get(&index->unmerged, n);-}--static int index_error_invalid(const char *message)-{- giterr_set(GITERR_INDEX, "Invalid data in index - %s", message);- return -1;-}--static int read_unmerged(git_index *index, const char *buffer, size_t size)-{- const char *endptr;- size_t len;- int i;-- if (git_vector_init(&index->unmerged, 16, unmerged_cmp) < 0)- return -1;-- while (size) {- git_index_entry_unmerged *lost;-- len = strlen(buffer) + 1;- if (size <= len)- return index_error_invalid("reading unmerged entries");-- lost = git__malloc(sizeof(git_index_entry_unmerged));- GITERR_CHECK_ALLOC(lost);-- if (git_vector_insert(&index->unmerged, lost) < 0)- return -1;-- /* read NUL-terminated pathname for entry */- lost->path = git__strdup(buffer);- GITERR_CHECK_ALLOC(lost->path);-- size -= len;- buffer += len;-- /* read 3 ASCII octal numbers for stage entries */- for (i = 0; i < 3; i++) {- int tmp;-- if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 ||- !endptr || endptr == buffer || *endptr ||- (unsigned)tmp > UINT_MAX)- return index_error_invalid("reading unmerged entry stage");-- lost->mode[i] = tmp;-- len = (endptr + 1) - buffer;- if (size <= len)- return index_error_invalid("reading unmerged entry stage");-- size -= len;- buffer += len;- }-- /* read up to 3 OIDs for stage entries */- for (i = 0; i < 3; i++) {- if (!lost->mode[i])- continue;- if (size < 20)- return index_error_invalid("reading unmerged entry oid");-- git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer);- size -= 20;- buffer += 20;- }- }-- return 0;-}--static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size)-{- size_t path_length, entry_size;- uint16_t flags_raw;- const char *path_ptr;- const struct entry_short *source = buffer;-- if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size)- return 0;-- memset(dest, 0x0, sizeof(git_index_entry));-- dest->ctime.seconds = (git_time_t)ntohl(source->ctime.seconds);- dest->ctime.nanoseconds = ntohl(source->ctime.nanoseconds);- dest->mtime.seconds = (git_time_t)ntohl(source->mtime.seconds);- dest->mtime.nanoseconds = ntohl(source->mtime.nanoseconds);- dest->dev = ntohl(source->dev);- dest->ino = ntohl(source->ino);- dest->mode = ntohl(source->mode);- dest->uid = ntohl(source->uid);- dest->gid = ntohl(source->gid);- dest->file_size = ntohl(source->file_size);- git_oid_cpy(&dest->oid, &source->oid);- dest->flags = ntohs(source->flags);-- if (dest->flags & GIT_IDXENTRY_EXTENDED) {- const struct entry_long *source_l = (const struct entry_long *)source;- path_ptr = source_l->path;-- flags_raw = ntohs(source_l->flags_extended);- memcpy(&dest->flags_extended, &flags_raw, 2);- } else- path_ptr = source->path;-- path_length = dest->flags & GIT_IDXENTRY_NAMEMASK;-- /* if this is a very long string, we must find its- * real length without overflowing */- if (path_length == 0xFFF) {- const char *path_end;-- path_end = memchr(path_ptr, '\0', buffer_size);- if (path_end == NULL)- return 0;-- path_length = path_end - path_ptr;- }-- if (dest->flags & GIT_IDXENTRY_EXTENDED)- entry_size = long_entry_size(path_length);- else- entry_size = short_entry_size(path_length);-- if (INDEX_FOOTER_SIZE + entry_size > buffer_size)- return 0;-- dest->path = git__strdup(path_ptr);- assert(dest->path);-- return entry_size;-}--static int read_header(struct index_header *dest, const void *buffer)-{- const struct index_header *source = buffer;-- dest->signature = ntohl(source->signature);- if (dest->signature != INDEX_HEADER_SIG)- return index_error_invalid("incorrect header signature");-- dest->version = ntohl(source->version);- if (dest->version != INDEX_VERSION_NUMBER_EXT &&- dest->version != INDEX_VERSION_NUMBER)- return index_error_invalid("incorrect header version");-- dest->entry_count = ntohl(source->entry_count);- return 0;-}--static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size)-{- const struct index_extension *source;- struct index_extension dest;- size_t total_size;-- source = (const struct index_extension *)(buffer);-- memcpy(dest.signature, source->signature, 4);- dest.extension_size = ntohl(source->extension_size);-- total_size = dest.extension_size + sizeof(struct index_extension);-- if (buffer_size - total_size < INDEX_FOOTER_SIZE)- return 0;-- /* optional extension */- if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') {- /* tree cache */- if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) {- if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < 0)- return 0;- } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) {- if (read_unmerged(index, buffer + 8, dest.extension_size) < 0)- return 0;- }- /* else, unsupported extension. We cannot parse this, but we can skip- * it by returning `total_size */- } else {- /* we cannot handle non-ignorable extensions;- * in fact they aren't even defined in the standard */- return 0;- }-- return total_size;-}--static int parse_index(git_index *index, const char *buffer, size_t buffer_size)-{- unsigned int i;- struct index_header header;- git_oid checksum_calculated, checksum_expected;--#define seek_forward(_increase) { \- if (_increase >= buffer_size) \- return index_error_invalid("ran out of data while parsing"); \- buffer += _increase; \- buffer_size -= _increase;\-}-- if (buffer_size < INDEX_HEADER_SIZE + INDEX_FOOTER_SIZE)- return index_error_invalid("insufficient buffer space");-- /* Precalculate the SHA1 of the files's contents -- we'll match it to- * the provided SHA1 in the footer */- git_hash_buf(&checksum_calculated, buffer, buffer_size - INDEX_FOOTER_SIZE);-- /* Parse header */- if (read_header(&header, buffer) < 0)- return -1;-- seek_forward(INDEX_HEADER_SIZE);-- git_vector_clear(&index->entries);-- /* Parse all the entries */- for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) {- size_t entry_size;- git_index_entry *entry;-- entry = git__malloc(sizeof(git_index_entry));- GITERR_CHECK_ALLOC(entry);-- entry_size = read_entry(entry, buffer, buffer_size);-- /* 0 bytes read means an object corruption */- if (entry_size == 0)- return index_error_invalid("invalid entry");-- if (git_vector_insert(&index->entries, entry) < 0)- return -1;-- seek_forward(entry_size);- }-- if (i != header.entry_count)- return index_error_invalid("header entries changed while parsing");-- /* There's still space for some extensions! */- while (buffer_size > INDEX_FOOTER_SIZE) {- size_t extension_size;-- extension_size = read_extension(index, buffer, buffer_size);-- /* see if we have read any bytes from the extension */- if (extension_size == 0)- return index_error_invalid("extension size is zero");-- seek_forward(extension_size);- }-- if (buffer_size != INDEX_FOOTER_SIZE)- return index_error_invalid("buffer size does not match index footer size");-- /* 160-bit SHA-1 over the content of the index file before this checksum. */- git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer);-- if (git_oid_cmp(&checksum_calculated, &checksum_expected) != 0)- return index_error_invalid("calculated checksum does not match expected");--#undef seek_forward-- /* force sorting in the vector: the entries are- * assured to be sorted on the index */- index->entries.sorted = 1;- return 0;-}--static int is_index_extended(git_index *index)-{- unsigned int i, extended;- git_index_entry *entry;-- extended = 0;-- git_vector_foreach(&index->entries, i, entry) {- entry->flags &= ~GIT_IDXENTRY_EXTENDED;- if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) {- extended++;- entry->flags |= GIT_IDXENTRY_EXTENDED;- }- }-- return extended;-}--static int write_disk_entry(git_filebuf *file, git_index_entry *entry)-{- void *mem = NULL;- struct entry_short *ondisk;- size_t path_len, disk_size;- char *path;-- path_len = strlen(entry->path);-- if (entry->flags & GIT_IDXENTRY_EXTENDED)- disk_size = long_entry_size(path_len);- else- disk_size = short_entry_size(path_len);-- if (git_filebuf_reserve(file, &mem, disk_size) < 0)- return -1;-- ondisk = (struct entry_short *)mem;-- memset(ondisk, 0x0, disk_size);-- /**- * Yes, we have to truncate.- *- * The on-disk format for Index entries clearly defines- * the time and size fields to be 4 bytes each -- so even if- * we store these values with 8 bytes on-memory, they must- * be truncated to 4 bytes before writing to disk.- *- * In 2038 I will be either too dead or too rich to care about this- */- ondisk->ctime.seconds = htonl((uint32_t)entry->ctime.seconds);- ondisk->mtime.seconds = htonl((uint32_t)entry->mtime.seconds);- ondisk->ctime.nanoseconds = htonl(entry->ctime.nanoseconds);- ondisk->mtime.nanoseconds = htonl(entry->mtime.nanoseconds);- ondisk->dev = htonl(entry->dev);- ondisk->ino = htonl(entry->ino);- ondisk->mode = htonl(entry->mode);- ondisk->uid = htonl(entry->uid);- ondisk->gid = htonl(entry->gid);- ondisk->file_size = htonl((uint32_t)entry->file_size);-- git_oid_cpy(&ondisk->oid, &entry->oid);-- ondisk->flags = htons(entry->flags);-- if (entry->flags & GIT_IDXENTRY_EXTENDED) {- struct entry_long *ondisk_ext;- ondisk_ext = (struct entry_long *)ondisk;- ondisk_ext->flags_extended = htons(entry->flags_extended);- path = ondisk_ext->path;- }- else- path = ondisk->path;-- memcpy(path, entry->path, path_len);-- return 0;-}--static int write_entries(git_index *index, git_filebuf *file)-{- unsigned int i;-- for (i = 0; i < index->entries.length; ++i) {- git_index_entry *entry;- entry = git_vector_get(&index->entries, i);- if (write_disk_entry(file, entry) < 0)- return -1;- }-- return 0;-}--static int write_index(git_index *index, git_filebuf *file)-{- git_oid hash_final;-- struct index_header header;-- int is_extended;-- assert(index && file);-- is_extended = is_index_extended(index);-- header.signature = htonl(INDEX_HEADER_SIG);- header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER);- header.entry_count = htonl(index->entries.length);-- if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0)- return -1;-- if (write_entries(index, file) < 0)- return -1;-- /* TODO: write extensions (tree cache) */-- /* get out the hash for all the contents we've appended to the file */- git_filebuf_hash(&hash_final, file);-- /* write it at the end of the file */- return git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ);-}--int git_index_entry_stage(const git_index_entry *entry)-{- return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;-}--static int read_tree_cb(const char *root, git_tree_entry *tentry, void *data)-{- git_index *index = data;- git_index_entry *entry = NULL;- git_buf path = GIT_BUF_INIT;-- if (git_tree_entry__is_tree(tentry))- return 0;-- if (git_buf_joinpath(&path, root, tentry->filename) < 0)- return -1;-- entry = git__calloc(1, sizeof(git_index_entry));- GITERR_CHECK_ALLOC(entry);-- entry->mode = tentry->attr;- entry->oid = tentry->oid;- entry->path = git_buf_detach(&path);- git_buf_free(&path);-- if (index_insert(index, entry, 0) < 0) {- index_entry_free(entry);- return -1;- }-- return 0;-}--int git_index_read_tree(git_index *index, git_tree *tree)-{- git_index_clear(index);-- return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index);+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include <stddef.h>++#include "common.h"+#include "repository.h"+#include "index.h"+#include "tree.h"+#include "tree-cache.h"+#include "hash.h"+#include "iterator.h"+#include "pathspec.h"+#include "git2/odb.h"+#include "git2/oid.h"+#include "git2/blob.h"+#include "git2/config.h"++#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7)+#define short_entry_size(len) entry_size(struct entry_short, len)+#define long_entry_size(len) entry_size(struct entry_long, len)++#define minimal_entry_size (offsetof(struct entry_short, path))++static const size_t INDEX_FOOTER_SIZE = GIT_OID_RAWSZ;+static const size_t INDEX_HEADER_SIZE = 12;++static const unsigned int INDEX_VERSION_NUMBER = 2;+static const unsigned int INDEX_VERSION_NUMBER_EXT = 3;++static const unsigned int INDEX_HEADER_SIG = 0x44495243;+static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'};+static const char INDEX_EXT_UNMERGED_SIG[] = {'R', 'E', 'U', 'C'};++#define INDEX_OWNER(idx) ((git_repository *)(GIT_REFCOUNT_OWNER(idx)))++struct index_header {+ uint32_t signature;+ uint32_t version;+ uint32_t entry_count;+};++struct index_extension {+ char signature[4];+ uint32_t extension_size;+};++struct entry_time {+ uint32_t seconds;+ uint32_t nanoseconds;+};++struct entry_short {+ struct entry_time ctime;+ struct entry_time mtime;+ uint32_t dev;+ uint32_t ino;+ uint32_t mode;+ uint32_t uid;+ uint32_t gid;+ uint32_t file_size;+ git_oid oid;+ uint16_t flags;+ char path[1]; /* arbitrary length */+};++struct entry_long {+ struct entry_time ctime;+ struct entry_time mtime;+ uint32_t dev;+ uint32_t ino;+ uint32_t mode;+ uint32_t uid;+ uint32_t gid;+ uint32_t file_size;+ git_oid oid;+ uint16_t flags;+ uint16_t flags_extended;+ char path[1]; /* arbitrary length */+};++struct entry_srch_key {+ const char *path;+ int stage;+};++/* local declarations */+static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size);+static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size);+static int read_header(struct index_header *dest, const void *buffer);++static int parse_index(git_index *index, const char *buffer, size_t buffer_size);+static bool is_index_extended(git_index *index);+static int write_index(git_index *index, git_filebuf *file);++static int index_find(size_t *at_pos, git_index *index, const char *path, int stage);++static void index_entry_free(git_index_entry *entry);+static void index_entry_reuc_free(git_index_reuc_entry *reuc);++GIT_INLINE(int) index_entry_stage(const git_index_entry *entry)+{+ return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;+}++static int index_srch(const void *key, const void *array_member)+{+ const struct entry_srch_key *srch_key = key;+ const git_index_entry *entry = array_member;+ int ret;++ ret = strcmp(srch_key->path, entry->path);++ if (ret == 0)+ ret = srch_key->stage - index_entry_stage(entry);++ return ret;+}++static int index_isrch(const void *key, const void *array_member)+{+ const struct entry_srch_key *srch_key = key;+ const git_index_entry *entry = array_member;+ int ret;++ ret = strcasecmp(srch_key->path, entry->path);++ if (ret == 0)+ ret = srch_key->stage - index_entry_stage(entry);++ return ret;+}++static int index_cmp_path(const void *a, const void *b)+{+ return strcmp((const char *)a, (const char *)b);+}++static int index_icmp_path(const void *a, const void *b)+{+ return strcasecmp((const char *)a, (const char *)b);+}++static int index_srch_path(const void *path, const void *array_member)+{+ const git_index_entry *entry = array_member;++ return strcmp((const char *)path, entry->path);+}++static int index_isrch_path(const void *path, const void *array_member)+{+ const git_index_entry *entry = array_member;++ return strcasecmp((const char *)path, entry->path);+}++static int index_cmp(const void *a, const void *b)+{+ int diff;+ const git_index_entry *entry_a = a;+ const git_index_entry *entry_b = b;++ diff = strcmp(entry_a->path, entry_b->path);++ if (diff == 0)+ diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b));++ return diff;+}++static int index_icmp(const void *a, const void *b)+{+ int diff;+ const git_index_entry *entry_a = a;+ const git_index_entry *entry_b = b;++ diff = strcasecmp(entry_a->path, entry_b->path);++ if (diff == 0)+ diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b));++ return diff;+}++static int reuc_srch(const void *key, const void *array_member)+{+ const git_index_reuc_entry *reuc = array_member;++ return strcmp(key, reuc->path);+}++static int reuc_isrch(const void *key, const void *array_member)+{+ const git_index_reuc_entry *reuc = array_member;++ return strcasecmp(key, reuc->path);+}++static int reuc_cmp(const void *a, const void *b)+{+ const git_index_reuc_entry *info_a = a;+ const git_index_reuc_entry *info_b = b;++ return strcmp(info_a->path, info_b->path);+}++static int reuc_icmp(const void *a, const void *b)+{+ const git_index_reuc_entry *info_a = a;+ const git_index_reuc_entry *info_b = b;++ return strcasecmp(info_a->path, info_b->path);+}++static unsigned int index_create_mode(unsigned int mode)+{+ if (S_ISLNK(mode))+ return S_IFLNK;++ if (S_ISDIR(mode) || (mode & S_IFMT) == (S_IFLNK | S_IFDIR))+ return (S_IFLNK | S_IFDIR);++ return S_IFREG | ((mode & 0100) ? 0755 : 0644);+}++static unsigned int index_merge_mode(+ git_index *index, git_index_entry *existing, unsigned int mode)+{+ if (index->no_symlinks && S_ISREG(mode) &&+ existing && S_ISLNK(existing->mode))+ return existing->mode;++ if (index->distrust_filemode && S_ISREG(mode))+ return (existing && S_ISREG(existing->mode)) ?+ existing->mode : index_create_mode(0666);++ return index_create_mode(mode);+}++void git_index__set_ignore_case(git_index *index, bool ignore_case)+{+ index->ignore_case = ignore_case;++ index->entries._cmp = ignore_case ? index_icmp : index_cmp;+ index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path;+ index->entries_search = ignore_case ? index_isrch : index_srch;+ index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path;+ index->entries.sorted = 0;+ git_vector_sort(&index->entries);++ index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp;+ index->reuc_search = ignore_case ? reuc_isrch : reuc_srch;+ index->reuc.sorted = 0;+ git_vector_sort(&index->reuc);+}++int git_index_open(git_index **index_out, const char *index_path)+{+ git_index *index;++ assert(index_out);++ index = git__calloc(1, sizeof(git_index));+ GITERR_CHECK_ALLOC(index);++ if (index_path != NULL) {+ index->index_file_path = git__strdup(index_path);+ GITERR_CHECK_ALLOC(index->index_file_path);++ /* Check if index file is stored on disk already */+ if (git_path_exists(index->index_file_path) == true)+ index->on_disk = 1;+ }++ if (git_vector_init(&index->entries, 32, index_cmp) < 0 ||+ git_vector_init(&index->reuc, 32, reuc_cmp) < 0)+ return -1;++ index->entries_cmp_path = index_cmp_path;+ index->entries_search = index_srch;+ index->entries_search_path = index_srch_path;+ index->reuc_search = reuc_srch;++ *index_out = index;+ GIT_REFCOUNT_INC(index);++ return (index_path != NULL) ? git_index_read(index) : 0;+}++int git_index_new(git_index **out)+{+ return git_index_open(out, NULL);+}++static void index_free(git_index *index)+{+ git_index_clear(index);+ git_vector_free(&index->entries);+ git_vector_free(&index->reuc);++ git__free(index->index_file_path);+ git__free(index);+}++void git_index_free(git_index *index)+{+ if (index == NULL)+ return;++ GIT_REFCOUNT_DEC(index, index_free);+}++void git_index_clear(git_index *index)+{+ size_t i;++ assert(index);++ for (i = 0; i < index->entries.length; ++i) {+ git_index_entry *e;+ e = git_vector_get(&index->entries, i);+ git__free(e->path);+ git__free(e);+ }+ git_vector_clear(&index->entries);++ git_index_reuc_clear(index);+ + git_futils_filestamp_set(&index->stamp, NULL);++ git_tree_cache_free(index->tree);+ index->tree = NULL;+}++static int create_index_error(int error, const char *msg)+{+ giterr_set(GITERR_INDEX, msg);+ return error;+}++int git_index_set_caps(git_index *index, unsigned int caps)+{+ int old_ignore_case;++ assert(index);++ old_ignore_case = index->ignore_case;++ if (caps == GIT_INDEXCAP_FROM_OWNER) {+ git_config *cfg;+ int val;++ if (INDEX_OWNER(index) == NULL ||+ git_repository_config__weakptr(&cfg, INDEX_OWNER(index)) < 0)+ return create_index_error(-1,+ "Cannot get repository config to set index caps");++ if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)+ index->ignore_case = (val != 0);+ if (git_config_get_bool(&val, cfg, "core.filemode") == 0)+ index->distrust_filemode = (val == 0);+ if (git_config_get_bool(&val, cfg, "core.symlinks") == 0)+ index->no_symlinks = (val == 0);+ }+ else {+ index->ignore_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0);+ index->distrust_filemode = ((caps & GIT_INDEXCAP_NO_FILEMODE) != 0);+ index->no_symlinks = ((caps & GIT_INDEXCAP_NO_SYMLINKS) != 0);+ }++ if (old_ignore_case != index->ignore_case) {+ git_index__set_ignore_case(index, index->ignore_case);+ }++ return 0;+}++unsigned int git_index_caps(const git_index *index)+{+ return ((index->ignore_case ? GIT_INDEXCAP_IGNORE_CASE : 0) |+ (index->distrust_filemode ? GIT_INDEXCAP_NO_FILEMODE : 0) |+ (index->no_symlinks ? GIT_INDEXCAP_NO_SYMLINKS : 0));+}++int git_index_read(git_index *index)+{+ int error = 0, updated;+ git_buf buffer = GIT_BUF_INIT;+ git_futils_filestamp stamp = {0};++ if (!index->index_file_path)+ return create_index_error(-1,+ "Failed to read index: The index is in-memory only");++ if (!index->on_disk || git_path_exists(index->index_file_path) == false) {+ git_index_clear(index);+ index->on_disk = 0;+ return 0;+ }++ updated = git_futils_filestamp_check(&stamp, index->index_file_path);+ if (updated <= 0)+ return updated;++ error = git_futils_readbuffer(&buffer, index->index_file_path);+ if (error < 0)+ return error;++ git_index_clear(index);+ error = parse_index(index, buffer.ptr, buffer.size);++ if (!error)+ git_futils_filestamp_set(&index->stamp, &stamp);++ git_buf_free(&buffer);+ return error;+}++int git_index_write(git_index *index)+{+ git_filebuf file = GIT_FILEBUF_INIT;+ int error;++ if (!index->index_file_path)+ return create_index_error(-1,+ "Failed to read index: The index is in-memory only");++ git_vector_sort(&index->entries);+ git_vector_sort(&index->reuc);++ if ((error = git_filebuf_open(+ &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0)+ return error;++ if ((error = write_index(index, &file)) < 0) {+ git_filebuf_cleanup(&file);+ return error;+ }++ if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0)+ return error;++ error = git_futils_filestamp_check(&index->stamp, index->index_file_path);+ if (error < 0)+ return error;++ index->on_disk = 1;+ return 0;+}++int git_index_write_tree(git_oid *oid, git_index *index)+{+ git_repository *repo;++ assert(oid && index);++ repo = INDEX_OWNER(index);++ if (repo == NULL)+ return create_index_error(-1, "Failed to write tree. "+ "The index file is not backed up by an existing repository");++ return git_tree__write_index(oid, index, repo);+}++int git_index_write_tree_to(git_oid *oid, git_index *index, git_repository *repo)+{+ assert(oid && index && repo);+ return git_tree__write_index(oid, index, repo);+}++size_t git_index_entrycount(const git_index *index)+{+ assert(index);+ return index->entries.length;+}++const git_index_entry *git_index_get_byindex(+ git_index *index, size_t n)+{+ assert(index);+ git_vector_sort(&index->entries);+ return git_vector_get(&index->entries, n);+}++const git_index_entry *git_index_get_bypath(+ git_index *index, const char *path, int stage)+{+ size_t pos;++ assert(index);++ git_vector_sort(&index->entries);++ if (index_find(&pos, index, path, stage) < 0)+ return NULL;++ return git_index_get_byindex(index, pos);+}++void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st)+{+ entry->ctime.seconds = (git_time_t)st->st_ctime;+ entry->mtime.seconds = (git_time_t)st->st_mtime;+ /* entry->mtime.nanoseconds = st->st_mtimensec; */+ /* entry->ctime.nanoseconds = st->st_ctimensec; */+ entry->dev = st->st_rdev;+ entry->ino = st->st_ino;+ entry->mode = index_create_mode(st->st_mode);+ entry->uid = st->st_uid;+ entry->gid = st->st_gid;+ entry->file_size = st->st_size;+}++int git_index_entry__cmp(const void *a, const void *b)+{+ const git_index_entry *entry_a = a;+ const git_index_entry *entry_b = b;++ return strcmp(entry_a->path, entry_b->path);+}++int git_index_entry__cmp_icase(const void *a, const void *b)+{+ const git_index_entry *entry_a = a;+ const git_index_entry *entry_b = b;++ return strcasecmp(entry_a->path, entry_b->path);+}++static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path)+{+ git_index_entry *entry = NULL;+ struct stat st;+ git_oid oid;+ const char *workdir;+ git_buf full_path = GIT_BUF_INIT;+ int error;++ if (INDEX_OWNER(index) == NULL)+ return create_index_error(-1,+ "Could not initialize index entry. "+ "Index is not backed up by an existing repository.");++ workdir = git_repository_workdir(INDEX_OWNER(index));++ if (!workdir)+ return create_index_error(GIT_EBAREREPO,+ "Could not initialize index entry. Repository is bare");++ if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0)+ return error;++ if ((error = git_path_lstat(full_path.ptr, &st)) < 0) {+ git_buf_free(&full_path);+ return error;+ }++ git_buf_free(&full_path); /* done with full path */++ /* There is no need to validate the rel_path here, since it will be+ * immediately validated by the call to git_blob_create_fromfile.+ */++ /* write the blob to disk and get the oid */+ if ((error = git_blob_create_fromworkdir(&oid, INDEX_OWNER(index), rel_path)) < 0)+ return error;++ entry = git__calloc(1, sizeof(git_index_entry));+ GITERR_CHECK_ALLOC(entry);++ git_index_entry__init_from_stat(entry, &st);++ entry->oid = oid;+ entry->path = git__strdup(rel_path);+ GITERR_CHECK_ALLOC(entry->path);++ *entry_out = entry;+ return 0;+}++static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,+ const char *path,+ int ancestor_mode, git_oid *ancestor_oid,+ int our_mode, git_oid *our_oid, int their_mode, git_oid *their_oid)+{+ git_index_reuc_entry *reuc = NULL;++ assert(reuc_out && path);++ *reuc_out = NULL;++ reuc = git__calloc(1, sizeof(git_index_reuc_entry));+ GITERR_CHECK_ALLOC(reuc);++ reuc->path = git__strdup(path);+ if (reuc->path == NULL)+ return -1;++ if ((reuc->mode[0] = ancestor_mode) > 0)+ git_oid_cpy(&reuc->oid[0], ancestor_oid);++ if ((reuc->mode[1] = our_mode) > 0)+ git_oid_cpy(&reuc->oid[1], our_oid);++ if ((reuc->mode[2] = their_mode) > 0)+ git_oid_cpy(&reuc->oid[2], their_oid);++ *reuc_out = reuc;+ return 0;+}++static void index_entry_reuc_free(git_index_reuc_entry *reuc)+{+ if (!reuc)+ return;++ git__free(reuc->path);+ git__free(reuc);+}++static git_index_entry *index_entry_dup(const git_index_entry *source_entry)+{+ git_index_entry *entry;++ entry = git__malloc(sizeof(git_index_entry));+ if (!entry)+ return NULL;++ memcpy(entry, source_entry, sizeof(git_index_entry));++ /* duplicate the path string so we own it */+ entry->path = git__strdup(entry->path);+ if (!entry->path)+ return NULL;++ return entry;+}++static void index_entry_free(git_index_entry *entry)+{+ if (!entry)+ return;+ git__free(entry->path);+ git__free(entry);+}++static int index_insert(git_index *index, git_index_entry *entry, int replace)+{+ size_t path_length, position;+ git_index_entry **existing = NULL;++ assert(index && entry && entry->path != NULL);++ /* make sure that the path length flag is correct */+ path_length = strlen(entry->path);++ entry->flags &= ~GIT_IDXENTRY_NAMEMASK;++ if (path_length < GIT_IDXENTRY_NAMEMASK)+ entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK;+ else+ entry->flags |= GIT_IDXENTRY_NAMEMASK;++ /* look if an entry with this path already exists */+ if (!index_find(&position, index, entry->path, index_entry_stage(entry))) {+ existing = (git_index_entry **)&index->entries.contents[position];++ /* update filemode to existing values if stat is not trusted */+ entry->mode = index_merge_mode(index, *existing, entry->mode);+ }++ /* if replacing is not requested or no existing entry exists, just+ * insert entry at the end; the index is no longer sorted+ */+ if (!replace || !existing)+ return git_vector_insert(&index->entries, entry);++ /* exists, replace it */+ git__free((*existing)->path);+ git__free(*existing);+ *existing = entry;++ return 0;+}++static int index_conflict_to_reuc(git_index *index, const char *path)+{+ git_index_entry *conflict_entries[3];+ int ancestor_mode, our_mode, their_mode;+ git_oid *ancestor_oid, *our_oid, *their_oid;+ int ret;++ if ((ret = git_index_conflict_get(&conflict_entries[0],+ &conflict_entries[1], &conflict_entries[2], index, path)) < 0)+ return ret;++ ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode;+ our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode;+ their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode;++ ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->oid;+ our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->oid;+ their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->oid;++ if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid,+ our_mode, our_oid, their_mode, their_oid)) >= 0)+ ret = git_index_conflict_remove(index, path);++ return ret;+}++int git_index_add_bypath(git_index *index, const char *path)+{+ git_index_entry *entry = NULL;+ int ret;++ assert(index && path);++ if ((ret = index_entry_init(&entry, index, path)) < 0 ||+ (ret = index_insert(index, entry, 1)) < 0)+ goto on_error;++ /* Adding implies conflict was resolved, move conflict entries to REUC */+ if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND)+ goto on_error;++ git_tree_cache_invalidate_path(index->tree, entry->path);+ return 0;++on_error:+ index_entry_free(entry);+ return ret;+}++int git_index_remove_bypath(git_index *index, const char *path)+{+ int ret;++ assert(index && path);++ if (((ret = git_index_remove(index, path, 0)) < 0 &&+ ret != GIT_ENOTFOUND) ||+ ((ret = index_conflict_to_reuc(index, path)) < 0 &&+ ret != GIT_ENOTFOUND))+ return ret;++ return 0;+}++int git_index_add(git_index *index, const git_index_entry *source_entry)+{+ git_index_entry *entry = NULL;+ int ret;++ entry = index_entry_dup(source_entry);+ if (entry == NULL)+ return -1;++ if ((ret = index_insert(index, entry, 1)) < 0) {+ index_entry_free(entry);+ return ret;+ }++ git_tree_cache_invalidate_path(index->tree, entry->path);+ return 0;+}++int git_index_remove(git_index *index, const char *path, int stage)+{+ size_t position;+ int error;+ git_index_entry *entry;++ git_vector_sort(&index->entries);++ if (index_find(&position, index, path, stage) < 0)+ return GIT_ENOTFOUND;++ entry = git_vector_get(&index->entries, position);+ if (entry != NULL)+ git_tree_cache_invalidate_path(index->tree, entry->path);++ error = git_vector_remove(&index->entries, position);++ if (!error)+ index_entry_free(entry);++ return error;+}++int git_index_remove_directory(git_index *index, const char *dir, int stage)+{+ git_buf pfx = GIT_BUF_INIT;+ int error = 0;+ size_t pos;+ git_index_entry *entry;++ if (git_buf_sets(&pfx, dir) < 0 || git_path_to_dir(&pfx) < 0)+ return -1;++ git_vector_sort(&index->entries);++ pos = git_index__prefix_position(index, pfx.ptr);++ while (1) {+ entry = git_vector_get(&index->entries, pos);+ if (!entry || git__prefixcmp(entry->path, pfx.ptr) != 0)+ break;++ if (index_entry_stage(entry) != stage) {+ ++pos;+ continue;+ }++ git_tree_cache_invalidate_path(index->tree, entry->path);++ if ((error = git_vector_remove(&index->entries, pos)) < 0)+ break;+ index_entry_free(entry);++ /* removed entry at 'pos' so we don't need to increment it */+ }++ git_buf_free(&pfx);++ return error;+}++static int index_find(size_t *at_pos, git_index *index, const char *path, int stage)+{+ struct entry_srch_key srch_key;++ assert(path);++ srch_key.path = path;+ srch_key.stage = stage;++ return git_vector_bsearch2(at_pos, &index->entries, index->entries_search, &srch_key);+}++int git_index_find(size_t *at_pos, git_index *index, const char *path)+{+ size_t pos;++ assert(index && path);++ if (git_vector_bsearch2(&pos, &index->entries, index->entries_search_path, path) < 0) {+ giterr_set(GITERR_INDEX, "Index does not contain %s", path);+ return GIT_ENOTFOUND;+ }++ /* Since our binary search only looked at path, we may be in the+ * middle of a list of stages.+ */+ while (pos > 0) {+ const git_index_entry *prev = git_vector_get(&index->entries, pos-1);++ if (index->entries_cmp_path(prev->path, path) != 0)+ break;++ --pos;+ }++ if (at_pos)+ *at_pos = pos;++ return 0;+}++size_t git_index__prefix_position(git_index *index, const char *path)+{+ struct entry_srch_key srch_key;+ size_t pos;++ srch_key.path = path;+ srch_key.stage = 0;++ git_vector_sort(&index->entries);+ git_vector_bsearch2(+ &pos, &index->entries, index->entries_search, &srch_key);++ return pos;+}++int git_index_conflict_add(git_index *index,+ const git_index_entry *ancestor_entry,+ const git_index_entry *our_entry,+ const git_index_entry *their_entry)+{+ git_index_entry *entries[3] = { 0 };+ unsigned short i;+ int ret = 0;++ assert (index);++ if ((ancestor_entry != NULL && (entries[0] = index_entry_dup(ancestor_entry)) == NULL) ||+ (our_entry != NULL && (entries[1] = index_entry_dup(our_entry)) == NULL) ||+ (their_entry != NULL && (entries[2] = index_entry_dup(their_entry)) == NULL))+ return -1;++ for (i = 0; i < 3; i++) {+ if (entries[i] == NULL)+ continue;++ /* Make sure stage is correct */+ entries[i]->flags = (entries[i]->flags & ~GIT_IDXENTRY_STAGEMASK) |+ ((i+1) << GIT_IDXENTRY_STAGESHIFT);++ if ((ret = index_insert(index, entries[i], 1)) < 0)+ goto on_error;+ }++ return 0;++on_error:+ for (i = 0; i < 3; i++) {+ if (entries[i] != NULL)+ index_entry_free(entries[i]);+ }++ return ret;+}++int git_index_conflict_get(git_index_entry **ancestor_out,+ git_index_entry **our_out,+ git_index_entry **their_out,+ git_index *index, const char *path)+{+ size_t pos, posmax;+ int stage;+ git_index_entry *conflict_entry;+ int error = GIT_ENOTFOUND;++ assert(ancestor_out && our_out && their_out && index && path);++ *ancestor_out = NULL;+ *our_out = NULL;+ *their_out = NULL;++ if (git_index_find(&pos, index, path) < 0)+ return GIT_ENOTFOUND;++ for (posmax = git_index_entrycount(index); pos < posmax; ++pos) {++ conflict_entry = git_vector_get(&index->entries, pos);++ if (index->entries_cmp_path(conflict_entry->path, path) != 0)+ break;++ stage = index_entry_stage(conflict_entry);++ switch (stage) {+ case 3:+ *their_out = conflict_entry;+ error = 0;+ break;+ case 2:+ *our_out = conflict_entry;+ error = 0;+ break;+ case 1:+ *ancestor_out = conflict_entry;+ error = 0;+ break;+ default:+ break;+ };+ }++ return error;+}++int git_index_conflict_remove(git_index *index, const char *path)+{+ size_t pos, posmax;+ git_index_entry *conflict_entry;+ int error = 0;++ assert(index && path);++ if (git_index_find(&pos, index, path) < 0)+ return GIT_ENOTFOUND;++ posmax = git_index_entrycount(index);++ while (pos < posmax) {+ conflict_entry = git_vector_get(&index->entries, pos);++ if (index->entries_cmp_path(conflict_entry->path, path) != 0)+ break;++ if (index_entry_stage(conflict_entry) == 0) {+ pos++;+ continue;+ }++ if ((error = git_vector_remove(&index->entries, pos)) < 0)+ return error;++ index_entry_free(conflict_entry);+ posmax--;+ }++ return 0;+}++static int index_conflicts_match(const git_vector *v, size_t idx)+{+ git_index_entry *entry = git_vector_get(v, idx);++ if (index_entry_stage(entry) > 0) {+ index_entry_free(entry);+ return 1;+ }++ return 0;+}++void git_index_conflict_cleanup(git_index *index)+{+ assert(index);+ git_vector_remove_matching(&index->entries, index_conflicts_match);+}++int git_index_has_conflicts(const git_index *index)+{+ size_t i;+ git_index_entry *entry;++ assert(index);++ git_vector_foreach(&index->entries, i, entry) {+ if (index_entry_stage(entry) > 0)+ return 1;+ }++ return 0;+}++unsigned int git_index_reuc_entrycount(git_index *index)+{+ assert(index);+ return (unsigned int)index->reuc.length;+}++static int index_reuc_insert(git_index *index, git_index_reuc_entry *reuc, int replace)+{+ git_index_reuc_entry **existing = NULL;+ size_t position;++ assert(index && reuc && reuc->path != NULL);++ if (!git_index_reuc_find(&position, index, reuc->path))+ existing = (git_index_reuc_entry **)&index->reuc.contents[position];++ if (!replace || !existing)+ return git_vector_insert(&index->reuc, reuc);++ /* exists, replace it */+ git__free((*existing)->path);+ git__free(*existing);+ *existing = reuc;++ return 0;+}++int git_index_reuc_add(git_index *index, const char *path,+ int ancestor_mode, git_oid *ancestor_oid,+ int our_mode, git_oid *our_oid,+ int their_mode, git_oid *their_oid)+{+ git_index_reuc_entry *reuc = NULL;+ int error = 0;++ assert(index && path);++ if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 ||+ (error = index_reuc_insert(index, reuc, 1)) < 0)+ {+ index_entry_reuc_free(reuc);+ return error;+ }++ return error;+} ++int git_index_reuc_find(size_t *at_pos, git_index *index, const char *path)+{+ return git_vector_bsearch2(at_pos, &index->reuc, index->reuc_search, path);+}++const git_index_reuc_entry *git_index_reuc_get_bypath(+ git_index *index, const char *path)+{+ size_t pos;+ assert(index && path);++ if (!index->reuc.length)+ return NULL;++ git_vector_sort(&index->reuc);++ if (git_index_reuc_find(&pos, index, path) < 0)+ return NULL;++ return git_vector_get(&index->reuc, pos);+}++const git_index_reuc_entry *git_index_reuc_get_byindex(+ git_index *index, size_t n)+{+ assert(index);++ git_vector_sort(&index->reuc);+ return git_vector_get(&index->reuc, n);+}++int git_index_reuc_remove(git_index *index, size_t position)+{+ int error;+ git_index_reuc_entry *reuc;++ git_vector_sort(&index->reuc);++ reuc = git_vector_get(&index->reuc, position);+ error = git_vector_remove(&index->reuc, position);++ if (!error)+ index_entry_reuc_free(reuc);++ return error;+}++void git_index_reuc_clear(git_index *index)+{+ size_t i;+ git_index_reuc_entry *reuc;++ assert(index);++ git_vector_foreach(&index->reuc, i, reuc) {+ git__free(reuc->path);+ git__free(reuc);+ }++ git_vector_clear(&index->reuc);+}++static int index_error_invalid(const char *message)+{+ giterr_set(GITERR_INDEX, "Invalid data in index - %s", message);+ return -1;+}++static int read_reuc(git_index *index, const char *buffer, size_t size)+{+ const char *endptr;+ size_t len;+ int i;++ /* This gets called multiple times, the vector might already be initialized */+ if (index->reuc._alloc_size == 0 && git_vector_init(&index->reuc, 16, reuc_cmp) < 0)+ return -1;++ while (size) {+ git_index_reuc_entry *lost;++ len = strlen(buffer) + 1;+ if (size <= len)+ return index_error_invalid("reading reuc entries");++ lost = git__malloc(sizeof(git_index_reuc_entry));+ GITERR_CHECK_ALLOC(lost);++ if (git_vector_insert(&index->reuc, lost) < 0)+ return -1;++ /* read NUL-terminated pathname for entry */+ lost->path = git__strdup(buffer);+ GITERR_CHECK_ALLOC(lost->path);++ size -= len;+ buffer += len;++ /* read 3 ASCII octal numbers for stage entries */+ for (i = 0; i < 3; i++) {+ int tmp;++ if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 ||+ !endptr || endptr == buffer || *endptr ||+ (unsigned)tmp > UINT_MAX)+ return index_error_invalid("reading reuc entry stage");++ lost->mode[i] = tmp;++ len = (endptr + 1) - buffer;+ if (size <= len)+ return index_error_invalid("reading reuc entry stage");++ size -= len;+ buffer += len;+ }++ /* read up to 3 OIDs for stage entries */+ for (i = 0; i < 3; i++) {+ if (!lost->mode[i])+ continue;+ if (size < 20)+ return index_error_invalid("reading reuc entry oid");++ git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer);+ size -= 20;+ buffer += 20;+ }+ }++ /* entries are guaranteed to be sorted on-disk */+ index->reuc.sorted = 1;++ return 0;+}++static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size)+{+ size_t path_length, entry_size;+ uint16_t flags_raw;+ const char *path_ptr;+ const struct entry_short *source = buffer;++ if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size)+ return 0;++ memset(dest, 0x0, sizeof(git_index_entry));++ dest->ctime.seconds = (git_time_t)ntohl(source->ctime.seconds);+ dest->ctime.nanoseconds = ntohl(source->ctime.nanoseconds);+ dest->mtime.seconds = (git_time_t)ntohl(source->mtime.seconds);+ dest->mtime.nanoseconds = ntohl(source->mtime.nanoseconds);+ dest->dev = ntohl(source->dev);+ dest->ino = ntohl(source->ino);+ dest->mode = ntohl(source->mode);+ dest->uid = ntohl(source->uid);+ dest->gid = ntohl(source->gid);+ dest->file_size = ntohl(source->file_size);+ git_oid_cpy(&dest->oid, &source->oid);+ dest->flags = ntohs(source->flags);++ if (dest->flags & GIT_IDXENTRY_EXTENDED) {+ const struct entry_long *source_l = (const struct entry_long *)source;+ path_ptr = source_l->path;++ flags_raw = ntohs(source_l->flags_extended);+ memcpy(&dest->flags_extended, &flags_raw, 2);+ } else+ path_ptr = source->path;++ path_length = dest->flags & GIT_IDXENTRY_NAMEMASK;++ /* if this is a very long string, we must find its+ * real length without overflowing */+ if (path_length == 0xFFF) {+ const char *path_end;++ path_end = memchr(path_ptr, '\0', buffer_size);+ if (path_end == NULL)+ return 0;++ path_length = path_end - path_ptr;+ }++ if (dest->flags & GIT_IDXENTRY_EXTENDED)+ entry_size = long_entry_size(path_length);+ else+ entry_size = short_entry_size(path_length);++ if (INDEX_FOOTER_SIZE + entry_size > buffer_size)+ return 0;++ dest->path = git__strdup(path_ptr);+ assert(dest->path);++ return entry_size;+}++static int read_header(struct index_header *dest, const void *buffer)+{+ const struct index_header *source = buffer;++ dest->signature = ntohl(source->signature);+ if (dest->signature != INDEX_HEADER_SIG)+ return index_error_invalid("incorrect header signature");++ dest->version = ntohl(source->version);+ if (dest->version != INDEX_VERSION_NUMBER_EXT &&+ dest->version != INDEX_VERSION_NUMBER)+ return index_error_invalid("incorrect header version");++ dest->entry_count = ntohl(source->entry_count);+ return 0;+}++static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size)+{+ const struct index_extension *source;+ struct index_extension dest;+ size_t total_size;++ source = (const struct index_extension *)(buffer);++ memcpy(dest.signature, source->signature, 4);+ dest.extension_size = ntohl(source->extension_size);++ total_size = dest.extension_size + sizeof(struct index_extension);++ if (buffer_size - total_size < INDEX_FOOTER_SIZE)+ return 0;++ /* optional extension */+ if (dest.signature[0] >= 'A' && dest.signature[0] <= 'Z') {+ /* tree cache */+ if (memcmp(dest.signature, INDEX_EXT_TREECACHE_SIG, 4) == 0) {+ if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < 0)+ return 0;+ } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) {+ if (read_reuc(index, buffer + 8, dest.extension_size) < 0)+ return 0;+ }+ /* else, unsupported extension. We cannot parse this, but we can skip+ * it by returning `total_size */+ } else {+ /* we cannot handle non-ignorable extensions;+ * in fact they aren't even defined in the standard */+ return 0;+ }++ return total_size;+}++static int parse_index(git_index *index, const char *buffer, size_t buffer_size)+{+ unsigned int i;+ struct index_header header;+ git_oid checksum_calculated, checksum_expected;++#define seek_forward(_increase) { \+ if (_increase >= buffer_size) \+ return index_error_invalid("ran out of data while parsing"); \+ buffer += _increase; \+ buffer_size -= _increase;\+}++ if (buffer_size < INDEX_HEADER_SIZE + INDEX_FOOTER_SIZE)+ return index_error_invalid("insufficient buffer space");++ /* Precalculate the SHA1 of the files's contents -- we'll match it to+ * the provided SHA1 in the footer */+ git_hash_buf(&checksum_calculated, buffer, buffer_size - INDEX_FOOTER_SIZE);++ /* Parse header */+ if (read_header(&header, buffer) < 0)+ return -1;++ seek_forward(INDEX_HEADER_SIZE);++ git_vector_clear(&index->entries);++ /* Parse all the entries */+ for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) {+ size_t entry_size;+ git_index_entry *entry;++ entry = git__malloc(sizeof(git_index_entry));+ GITERR_CHECK_ALLOC(entry);++ entry_size = read_entry(entry, buffer, buffer_size);++ /* 0 bytes read means an object corruption */+ if (entry_size == 0)+ return index_error_invalid("invalid entry");++ if (git_vector_insert(&index->entries, entry) < 0)+ return -1;++ seek_forward(entry_size);+ }++ if (i != header.entry_count)+ return index_error_invalid("header entries changed while parsing");++ /* There's still space for some extensions! */+ while (buffer_size > INDEX_FOOTER_SIZE) {+ size_t extension_size;++ extension_size = read_extension(index, buffer, buffer_size);++ /* see if we have read any bytes from the extension */+ if (extension_size == 0)+ return index_error_invalid("extension size is zero");++ seek_forward(extension_size);+ }++ if (buffer_size != INDEX_FOOTER_SIZE)+ return index_error_invalid("buffer size does not match index footer size");++ /* 160-bit SHA-1 over the content of the index file before this checksum. */+ git_oid_fromraw(&checksum_expected, (const unsigned char *)buffer);++ if (git_oid_cmp(&checksum_calculated, &checksum_expected) != 0)+ return index_error_invalid("calculated checksum does not match expected");++#undef seek_forward++ /* Entries are stored case-sensitively on disk. */+ index->entries.sorted = !index->ignore_case;+ git_vector_sort(&index->entries);++ return 0;+}++static bool is_index_extended(git_index *index)+{+ size_t i, extended;+ git_index_entry *entry;++ extended = 0;++ git_vector_foreach(&index->entries, i, entry) {+ entry->flags &= ~GIT_IDXENTRY_EXTENDED;+ if (entry->flags_extended & GIT_IDXENTRY_EXTENDED_FLAGS) {+ extended++;+ entry->flags |= GIT_IDXENTRY_EXTENDED;+ }+ }++ return (extended > 0);+}++static int write_disk_entry(git_filebuf *file, git_index_entry *entry)+{+ void *mem = NULL;+ struct entry_short *ondisk;+ size_t path_len, disk_size;+ char *path;++ path_len = strlen(entry->path);++ if (entry->flags & GIT_IDXENTRY_EXTENDED)+ disk_size = long_entry_size(path_len);+ else+ disk_size = short_entry_size(path_len);++ if (git_filebuf_reserve(file, &mem, disk_size) < 0)+ return -1;++ ondisk = (struct entry_short *)mem;++ memset(ondisk, 0x0, disk_size);++ /**+ * Yes, we have to truncate.+ *+ * The on-disk format for Index entries clearly defines+ * the time and size fields to be 4 bytes each -- so even if+ * we store these values with 8 bytes on-memory, they must+ * be truncated to 4 bytes before writing to disk.+ *+ * In 2038 I will be either too dead or too rich to care about this+ */+ ondisk->ctime.seconds = htonl((uint32_t)entry->ctime.seconds);+ ondisk->mtime.seconds = htonl((uint32_t)entry->mtime.seconds);+ ondisk->ctime.nanoseconds = htonl(entry->ctime.nanoseconds);+ ondisk->mtime.nanoseconds = htonl(entry->mtime.nanoseconds);+ ondisk->dev = htonl(entry->dev);+ ondisk->ino = htonl(entry->ino);+ ondisk->mode = htonl(entry->mode);+ ondisk->uid = htonl(entry->uid);+ ondisk->gid = htonl(entry->gid);+ ondisk->file_size = htonl((uint32_t)entry->file_size);++ git_oid_cpy(&ondisk->oid, &entry->oid);++ ondisk->flags = htons(entry->flags);++ if (entry->flags & GIT_IDXENTRY_EXTENDED) {+ struct entry_long *ondisk_ext;+ ondisk_ext = (struct entry_long *)ondisk;+ ondisk_ext->flags_extended = htons(entry->flags_extended);+ path = ondisk_ext->path;+ }+ else+ path = ondisk->path;++ memcpy(path, entry->path, path_len);++ return 0;+}++static int write_entries(git_index *index, git_filebuf *file)+{+ int error = 0;+ size_t i;+ git_vector case_sorted;+ git_index_entry *entry;+ git_vector *out = &index->entries;++ /* If index->entries is sorted case-insensitively, then we need+ * to re-sort it case-sensitively before writing */+ if (index->ignore_case) {+ git_vector_dup(&case_sorted, &index->entries, index_cmp);+ git_vector_sort(&case_sorted);+ out = &case_sorted;+ }++ git_vector_foreach(out, i, entry)+ if ((error = write_disk_entry(file, entry)) < 0)+ break;++ if (index->ignore_case)+ git_vector_free(&case_sorted);++ return error;+}++static int write_extension(git_filebuf *file, struct index_extension *header, git_buf *data)+{+ struct index_extension ondisk;+ int error = 0;++ memset(&ondisk, 0x0, sizeof(struct index_extension));+ memcpy(&ondisk, header, 4);+ ondisk.extension_size = htonl(header->extension_size);++ if ((error = git_filebuf_write(file, &ondisk, sizeof(struct index_extension))) == 0)+ error = git_filebuf_write(file, data->ptr, data->size);++ return error;+}++static int create_reuc_extension_data(git_buf *reuc_buf, git_index_reuc_entry *reuc)+{+ int i;+ int error = 0;++ if ((error = git_buf_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0)+ return error;++ for (i = 0; i < 3; i++) {+ if ((error = git_buf_printf(reuc_buf, "%o", reuc->mode[i])) < 0 ||+ (error = git_buf_put(reuc_buf, "\0", 1)) < 0)+ return error;+ }++ for (i = 0; i < 3; i++) {+ if (reuc->mode[i] && (error = git_buf_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0)+ return error;+ }++ return 0;+}++static int write_reuc_extension(git_index *index, git_filebuf *file)+{+ git_buf reuc_buf = GIT_BUF_INIT;+ git_vector *out = &index->reuc;+ git_index_reuc_entry *reuc;+ struct index_extension extension;+ size_t i;+ int error = 0;++ git_vector_foreach(out, i, reuc) {+ if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0)+ goto done;+ }++ memset(&extension, 0x0, sizeof(struct index_extension));+ memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4);+ extension.extension_size = (uint32_t)reuc_buf.size;++ error = write_extension(file, &extension, &reuc_buf);++ git_buf_free(&reuc_buf);++done:+ return error;+}++static int write_index(git_index *index, git_filebuf *file)+{+ git_oid hash_final;+ struct index_header header;+ bool is_extended;++ assert(index && file);++ is_extended = is_index_extended(index);++ header.signature = htonl(INDEX_HEADER_SIG);+ header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER);+ header.entry_count = htonl((uint32_t)index->entries.length);++ if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0)+ return -1;++ if (write_entries(index, file) < 0)+ return -1;++ /* TODO: write tree cache extension */++ /* write the reuc extension */+ if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0)+ return -1;++ /* get out the hash for all the contents we've appended to the file */+ git_filebuf_hash(&hash_final, file);++ /* write it at the end of the file */+ return git_filebuf_write(file, hash_final.id, GIT_OID_RAWSZ);+}++int git_index_entry_stage(const git_index_entry *entry)+{+ return index_entry_stage(entry);+}++typedef struct read_tree_data {+ git_index *index;+ git_transfer_progress *stats;+} read_tree_data;++static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data)+{+ git_index *index = (git_index *)data;+ git_index_entry *entry = NULL;+ git_buf path = GIT_BUF_INIT;++ if (git_tree_entry__is_tree(tentry))+ return 0;++ if (git_buf_joinpath(&path, root, tentry->filename) < 0)+ return -1;++ entry = git__calloc(1, sizeof(git_index_entry));+ GITERR_CHECK_ALLOC(entry);++ entry->mode = tentry->attr;+ entry->oid = tentry->oid;++ if (path.size < GIT_IDXENTRY_NAMEMASK)+ entry->flags = path.size & GIT_IDXENTRY_NAMEMASK;+ else+ entry->flags = GIT_IDXENTRY_NAMEMASK;++ entry->path = git_buf_detach(&path);+ git_buf_free(&path);++ if (git_vector_insert(&index->entries, entry) < 0) {+ index_entry_free(entry);+ return -1;+ }++ return 0;+}++int git_index_read_tree(git_index *index, const git_tree *tree)+{+ git_index_clear(index);++ return git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, index);+}++git_repository *git_index_owner(const git_index *index)+{+ return INDEX_OWNER(index); }
libgit2/src/index.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -22,17 +22,32 @@ char *index_file_path; - time_t last_modified;+ git_futils_filestamp stamp; git_vector entries; unsigned int on_disk:1;++ unsigned int ignore_case:1;+ unsigned int distrust_filemode:1;+ unsigned int no_symlinks:1;+ git_tree_cache *tree; - git_vector unmerged;+ git_vector reuc;++ git_vector_cmp entries_cmp_path;+ git_vector_cmp entries_search;+ git_vector_cmp entries_search_path;+ git_vector_cmp reuc_search; }; -extern void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry);+extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st); -extern unsigned int git_index__prefix_position(git_index *index, const char *path);+extern size_t git_index__prefix_position(git_index *index, const char *path);++extern int git_index_entry__cmp(const void *a, const void *b);+extern int git_index_entry__cmp_icase(const void *a, const void *b);++extern void git_index__set_ignore_case(git_index *index, bool ignore_case); #endif
libgit2/src/indexer.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -17,7 +17,7 @@ #include "posix.h" #include "pack.h" #include "filebuf.h"-#include "sha1.h"+#include "oidmap.h" #define UINT31_MAX (0x7FFFFFFF) @@ -28,39 +28,32 @@ uint64_t offset_long; }; -struct git_indexer {- struct git_pack_file *pack;- size_t nr_objects;- git_vector objects;- git_filebuf file;- unsigned int fanout[256];- git_oid hash;-};- struct git_indexer_stream { unsigned int parsed_header :1,- opened_pack;+ opened_pack :1,+ have_stream :1,+ have_delta :1; struct git_pack_file *pack; git_filebuf pack_file;- git_filebuf index_file; git_off_t off;+ git_off_t entry_start;+ git_packfile_stream stream; size_t nr_objects; git_vector objects; git_vector deltas; unsigned int fanout[256];+ git_hash_ctx hash_ctx; git_oid hash;+ git_transfer_progress_callback progress_cb;+ void *progress_payload;+ char objbuf[8*1024]; }; struct delta_info { git_off_t delta_off; }; -const git_oid *git_indexer_hash(git_indexer *idx)-{- return &idx->hash;-}--const git_oid *git_indexer_stream_hash(git_indexer_stream *idx)+const git_oid *git_indexer_stream_hash(const git_indexer_stream *idx) { return &idx->hash; }@@ -130,23 +123,21 @@ return git_oid_cmp(&entrya->oid, &entryb->oid); } -static int cache_cmp(const void *a, const void *b)-{- const struct git_pack_entry *ea = a;- const struct git_pack_entry *eb = b;-- return git_oid_cmp(&ea->sha1, &eb->sha1);-}--int git_indexer_stream_new(git_indexer_stream **out, const char *prefix)+int git_indexer_stream_new(+ git_indexer_stream **out,+ const char *prefix,+ git_transfer_progress_callback progress_cb,+ void *progress_payload) { git_indexer_stream *idx; git_buf path = GIT_BUF_INIT;- static const char suff[] = "/objects/pack/pack-received";+ static const char suff[] = "/pack"; int error; idx = git__calloc(1, sizeof(git_indexer_stream)); GITERR_CHECK_ALLOC(idx);+ idx->progress_cb = progress_cb;+ idx->progress_payload = progress_payload; error = git_buf_joinpath(&path, prefix, suff); if (error < 0)@@ -171,69 +162,171 @@ /* Try to store the delta so we can try to resolve it later */ static int store_delta(git_indexer_stream *idx) {- git_otype type;- git_mwindow *w = NULL;- git_mwindow_file *mwf = &idx->pack->mwf;- git_off_t entry_start = idx->off; struct delta_info *delta;- size_t entry_size;- git_rawobj obj;- int error; - /*- * ref-delta objects can refer to object that we haven't- * found yet, so give it another opportunity- */- if (git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off) < 0)+ delta = git__calloc(1, sizeof(struct delta_info));+ GITERR_CHECK_ALLOC(delta);+ delta->delta_off = idx->entry_start;++ if (git_vector_insert(&idx->deltas, delta) < 0) return -1; - git_mwindow_close(&w);+ return 0;+} - /* If it's not a delta, mark it as failure, we can't do anything with it */- if (type != GIT_OBJ_REF_DELTA && type != GIT_OBJ_OFS_DELTA)- return -1;+static void hash_header(git_hash_ctx *ctx, git_off_t len, git_otype type)+{+ char buffer[64];+ size_t hdrlen; + hdrlen = git_odb__format_object_header(buffer, sizeof(buffer), (size_t)len, type);+ git_hash_update(ctx, buffer, hdrlen);+}++static int hash_object_stream(git_indexer_stream *idx, git_packfile_stream *stream)+{+ ssize_t read;++ assert(idx && stream);++ do {+ if ((read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf))) < 0)+ break;++ git_hash_update(&idx->hash_ctx, idx->objbuf, read);+ } while (read > 0);++ if (read < 0)+ return (int)read;++ return 0;+}++/* In order to create the packfile stream, we need to skip over the delta base description */+static int advance_delta_offset(git_indexer_stream *idx, git_otype type)+{+ git_mwindow *w = NULL;++ assert(type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA);+ if (type == GIT_OBJ_REF_DELTA) { idx->off += GIT_OID_RAWSZ; } else {- git_off_t base_off;-- base_off = get_delta_base(idx->pack, &w, &idx->off, type, entry_start);+ git_off_t base_off = get_delta_base(idx->pack, &w, &idx->off, type, idx->entry_start); git_mwindow_close(&w); if (base_off < 0) return (int)base_off; } - error = packfile_unpack_compressed(&obj, idx->pack, &w, &idx->off, entry_size, type);- if (error == GIT_EBUFS) {- idx->off = entry_start;- return GIT_EBUFS;- } else if (error < 0){- return -1;+ return 0;+}++/* Read from the stream and discard any output */+static int read_object_stream(git_indexer_stream *idx, git_packfile_stream *stream)+{+ ssize_t read;++ assert(stream);++ do {+ read = git_packfile_stream_read(stream, idx->objbuf, sizeof(idx->objbuf));+ } while (read > 0);++ if (read < 0)+ return (int)read;++ return 0;+}++static int crc_object(uint32_t *crc_out, git_mwindow_file *mwf, git_off_t start, git_off_t size)+{+ void *ptr;+ uint32_t crc;+ unsigned int left, len;+ git_mwindow *w = NULL;++ crc = crc32(0L, Z_NULL, 0);+ while (size) {+ ptr = git_mwindow_open(mwf, &w, start, (size_t)size, &left);+ if (ptr == NULL)+ return -1;++ len = min(left, (unsigned int)size);+ crc = crc32(crc, ptr, len);+ size -= len;+ start += len;+ git_mwindow_close(&w); } - delta = git__calloc(1, sizeof(struct delta_info));- GITERR_CHECK_ALLOC(delta);- delta->delta_off = entry_start;+ *crc_out = htonl(crc);+ return 0;+} - git__free(obj.data);+static int store_object(git_indexer_stream *idx)+{+ int i, error;+ khiter_t k;+ git_oid oid;+ struct entry *entry;+ git_off_t entry_size;+ struct git_pack_entry *pentry;+ git_hash_ctx *ctx = &idx->hash_ctx;+ git_off_t entry_start = idx->entry_start; - if (git_vector_insert(&idx->deltas, delta) < 0)- return -1;+ entry = git__calloc(1, sizeof(*entry));+ GITERR_CHECK_ALLOC(entry); + pentry = git__malloc(sizeof(struct git_pack_entry));+ GITERR_CHECK_ALLOC(pentry);++ git_hash_final(&oid, ctx);+ entry_size = idx->off - entry_start;+ if (entry_start > UINT31_MAX) {+ entry->offset = UINT32_MAX;+ entry->offset_long = entry_start;+ } else {+ entry->offset = (uint32_t)entry_start;+ }++ git_oid_cpy(&pentry->sha1, &oid);+ pentry->offset = entry_start;++ k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error);+ if (!error) {+ git__free(pentry);+ goto on_error;+ }++ kh_value(idx->pack->idx_cache, k) = pentry;++ git_oid_cpy(&entry->oid, &oid);++ if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0)+ goto on_error;++ /* Add the object to the list */+ if (git_vector_insert(&idx->objects, entry) < 0)+ goto on_error;++ for (i = oid.id[0]; i < 256; ++i) {+ idx->fanout[i]++;+ }+ return 0;++on_error:+ git__free(entry);++ return -1; } static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t entry_start) {- int i;+ int i, error;+ khiter_t k; git_oid oid;- void *packed; size_t entry_size;- unsigned int left; struct entry *entry;- git_mwindow *w = NULL;- git_mwindow_file *mwf = &idx->pack->mwf; struct git_pack_entry *pentry; entry = git__calloc(1, sizeof(*entry));@@ -257,20 +350,21 @@ git_oid_cpy(&pentry->sha1, &oid); pentry->offset = entry_start;- if (git_vector_insert(&idx->pack->cache, pentry) < 0)+ k = kh_put(oid, idx->pack->idx_cache, &pentry->sha1, &error);+ if (!error) {+ git__free(pentry); goto on_error;+ } + kh_value(idx->pack->idx_cache, k) = pentry;+ git_oid_cpy(&entry->oid, &oid); entry->crc = crc32(0L, Z_NULL, 0); entry_size = (size_t)(idx->off - entry_start);- packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left);- if (packed == NULL)+ if (crc_object(&entry->crc, &idx->pack->mwf, entry_start, entry_size) < 0) goto on_error; - entry->crc = htonl(crc32(entry->crc, packed, (uInt)entry_size));- git_mwindow_close(&w);- /* Add the object to the list */ if (git_vector_insert(&idx->objects, entry) < 0) goto on_error;@@ -283,20 +377,27 @@ on_error: git__free(entry);- git__free(pentry); git__free(obj->data); return -1; } -int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats)+static int do_progress_callback(git_indexer_stream *idx, git_transfer_progress *stats) {- int error;+ if (!idx->progress_cb) return 0;+ return idx->progress_cb(stats, idx->progress_payload);+}++int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats)+{+ int error = -1; struct git_pack_header hdr;- size_t processed = stats->processed;+ size_t processed; git_mwindow_file *mwf = &idx->pack->mwf; assert(idx && data && stats); + processed = stats->indexed_objects;+ if (git_filebuf_write(&idx->pack_file, data, size) < 0) return -1; @@ -311,11 +412,11 @@ mwf = &idx->pack->mwf; if (git_mwindow_file_register(&idx->pack->mwf) < 0) return -1;-- return 0; } if (!idx->parsed_header) {+ unsigned int total_objects;+ if ((unsigned)idx->pack->mwf.size < sizeof(hdr)) return 0; @@ -328,19 +429,25 @@ /* for now, limit to 2^32 objects */ assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects));+ if (idx->nr_objects == (size_t)((unsigned int)idx->nr_objects))+ total_objects = (unsigned int)idx->nr_objects;+ else+ total_objects = UINT_MAX; - if (git_vector_init(&idx->pack->cache, (unsigned int)idx->nr_objects, cache_cmp) < 0)- return -1;+ idx->pack->idx_cache = git_oidmap_alloc();+ GITERR_CHECK_ALLOC(idx->pack->idx_cache); idx->pack->has_cache = 1;- if (git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp) < 0)+ if (git_vector_init(&idx->objects, total_objects, objects_cmp) < 0) return -1; - if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0)+ if (git_vector_init(&idx->deltas, total_objects / 2, NULL) < 0) return -1; - stats->total = (unsigned int)idx->nr_objects;- stats->processed = 0;+ stats->received_objects = 0;+ processed = stats->indexed_objects = 0;+ stats->total_objects = total_objects;+ do_progress_callback(idx, stats); } /* Now that we have data in the pack, let's try to parse it */@@ -348,42 +455,91 @@ /* As the file grows any windows we try to use will be out of date */ git_mwindow_free_all(mwf); while (processed < idx->nr_objects) {- git_rawobj obj;+ git_packfile_stream *stream = &idx->stream; git_off_t entry_start = idx->off;+ size_t entry_size;+ git_otype type;+ git_mwindow *w = NULL; if (idx->pack->mwf.size <= idx->off + 20) return 0; - error = git_packfile_unpack(&obj, idx->pack, &idx->off);- if (error == GIT_EBUFS) {- idx->off = entry_start;- return 0;- }-- if (error < 0) {- idx->off = entry_start;- error = store_delta(idx);- if (error == GIT_EBUFS)+ if (!idx->have_stream) {+ error = git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off);+ if (error == GIT_EBUFS) {+ idx->off = entry_start; return 0;+ } if (error < 0)- return error;+ return -1; - continue;+ git_mwindow_close(&w);+ idx->entry_start = entry_start;+ git_hash_ctx_init(&idx->hash_ctx);++ if (type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA) {+ error = advance_delta_offset(idx, type);+ if (error == GIT_EBUFS) {+ idx->off = entry_start;+ return 0;+ }+ if (error < 0)+ return -1;++ idx->have_delta = 1;+ } else {+ idx->have_delta = 0;+ hash_header(&idx->hash_ctx, entry_size, type);+ }++ idx->have_stream = 1;+ if (git_packfile_stream_open(stream, idx->pack, idx->off) < 0)+ goto on_error;+ } - if (hash_and_save(idx, &obj, entry_start) < 0)+ if (idx->have_delta) {+ error = read_object_stream(idx, stream);+ } else {+ error = hash_object_stream(idx, stream);+ }++ idx->off = stream->curpos;+ if (error == GIT_EBUFS)+ return 0;++ /* We want to free the stream reasorces no matter what here */+ idx->have_stream = 0;+ git_packfile_stream_free(stream);++ if (error < 0) goto on_error; - git__free(obj.data);+ if (idx->have_delta) {+ error = store_delta(idx);+ } else {+ error = store_object(idx);+ } - stats->processed = (unsigned int)++processed;+ if (error < 0)+ goto on_error;++ if (!idx->have_delta) {+ stats->indexed_objects = (unsigned int)++processed;+ }+ stats->received_objects++;++ if (do_progress_callback(idx, stats) != 0) {+ error = GIT_EUSER;+ goto on_error;+ } } return 0; on_error: git_mwindow_free_all(mwf);- return -1;+ return error; } static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char *suffix)@@ -408,7 +564,7 @@ return git_buf_oom(path) ? -1 : 0; } -static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats)+static int resolve_deltas(git_indexer_stream *idx, git_transfer_progress *stats) { unsigned int i; struct delta_info *delta;@@ -424,13 +580,14 @@ return -1; git__free(obj.data);- stats->processed++;+ stats->indexed_objects++;+ do_progress_callback(idx, stats); } return 0; } -int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats)+int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats) { git_mwindow *w = NULL; unsigned int i, long_offsets = 0, left;@@ -439,11 +596,15 @@ struct entry *entry; void *packfile_hash; git_oid file_hash;- SHA_CTX ctx;+ git_hash_ctx ctx;+ git_filebuf index_file = {0}; + if (git_hash_ctx_init(&ctx) < 0)+ return -1;+ /* Test for this before resolve_deltas(), as it plays with idx->off */ if (idx->off < idx->pack->mwf.size - GIT_OID_RAWSZ) {- giterr_set(GITERR_INDEXER, "Indexing error: junk at the end of the pack");+ giterr_set(GITERR_INDEXER, "Indexing error: unexpected data at the end of the pack"); return -1; } @@ -451,7 +612,7 @@ if (resolve_deltas(idx, stats) < 0) return -1; - if (stats->processed != stats->total) {+ if (stats->indexed_objects != stats->total_objects) { giterr_set(GITERR_INDEXER, "Indexing error: early EOF"); return -1; }@@ -464,31 +625,30 @@ if (git_buf_oom(&filename)) return -1; - if (git_filebuf_open(&idx->index_file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS) < 0)+ if (git_filebuf_open(&index_file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS) < 0) goto on_error; /* Write out the header */ hdr.idx_signature = htonl(PACK_IDX_SIGNATURE); hdr.idx_version = htonl(2);- git_filebuf_write(&idx->index_file, &hdr, sizeof(hdr));+ git_filebuf_write(&index_file, &hdr, sizeof(hdr)); /* Write out the fanout table */ for (i = 0; i < 256; ++i) { uint32_t n = htonl(idx->fanout[i]);- git_filebuf_write(&idx->index_file, &n, sizeof(n));+ git_filebuf_write(&index_file, &n, sizeof(n)); } /* Write out the object names (SHA-1 hashes) */- SHA1_Init(&ctx); git_vector_foreach(&idx->objects, i, entry) {- git_filebuf_write(&idx->index_file, &entry->oid, sizeof(git_oid));- SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ);+ git_filebuf_write(&index_file, &entry->oid, sizeof(git_oid));+ git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ); }- SHA1_Final(idx->hash.id, &ctx);+ git_hash_final(&idx->hash, &ctx); /* Write out the CRC32 values */ git_vector_foreach(&idx->objects, i, entry) {- git_filebuf_write(&idx->index_file, &entry->crc, sizeof(uint32_t));+ git_filebuf_write(&index_file, &entry->crc, sizeof(uint32_t)); } /* Write out the offsets */@@ -500,7 +660,7 @@ else n = htonl(entry->offset); - git_filebuf_write(&idx->index_file, &n, sizeof(uint32_t));+ git_filebuf_write(&index_file, &n, sizeof(uint32_t)); } /* Write out the long offsets */@@ -513,7 +673,7 @@ split[0] = htonl(entry->offset_long >> 32); split[1] = htonl(entry->offset_long & 0xffffffff); - git_filebuf_write(&idx->index_file, &split, sizeof(uint32_t) * 2);+ git_filebuf_write(&index_file, &split, sizeof(uint32_t) * 2); } /* Write out the packfile trailer */@@ -526,24 +686,26 @@ memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ); git_mwindow_close(&w); - git_filebuf_write(&idx->index_file, &file_hash, sizeof(git_oid));+ git_filebuf_write(&index_file, &file_hash, sizeof(git_oid)); /* Write out the packfile trailer to the idx file as well */- if (git_filebuf_hash(&file_hash, &idx->index_file) < 0)+ if (git_filebuf_hash(&file_hash, &index_file) < 0) goto on_error; - git_filebuf_write(&idx->index_file, &file_hash, sizeof(git_oid));+ git_filebuf_write(&index_file, &file_hash, sizeof(git_oid)); /* Figure out what the final name should be */ if (index_path_stream(&filename, idx, ".idx") < 0) goto on_error; /* Commit file */- if (git_filebuf_commit_at(&idx->index_file, filename.ptr, GIT_PACK_FILE_MODE) < 0)+ if (git_filebuf_commit_at(&index_file, filename.ptr, GIT_PACK_FILE_MODE) < 0) goto on_error; git_mwindow_free_all(&idx->pack->mwf);+ /* We need to close the descriptor here so Windows doesn't choke on commit_at */ p_close(idx->pack->mwf.fd);+ idx->pack->mwf.fd = -1; if (index_path_stream(&filename, idx, ".pack") < 0) goto on_error;@@ -556,17 +718,17 @@ on_error: git_mwindow_free_all(&idx->pack->mwf);- p_close(idx->pack->mwf.fd);- git_filebuf_cleanup(&idx->index_file);+ git_filebuf_cleanup(&index_file); git_buf_free(&filename);+ git_hash_ctx_cleanup(&ctx); return -1; } void git_indexer_stream_free(git_indexer_stream *idx) {+ khiter_t k; unsigned int i; struct entry *e;- struct git_pack_entry *pe; struct delta_info *delta; if (idx == NULL)@@ -575,325 +737,20 @@ git_vector_foreach(&idx->objects, i, e) git__free(e); git_vector_free(&idx->objects);- git_vector_foreach(&idx->pack->cache, i, pe)- git__free(pe);- git_vector_free(&idx->pack->cache);- git_vector_foreach(&idx->deltas, i, delta)- git__free(delta);- git_vector_free(&idx->deltas);- git__free(idx->pack);- git__free(idx);-} -int git_indexer_new(git_indexer **out, const char *packname)-{- git_indexer *idx;- struct git_pack_header hdr;- int error;-- assert(out && packname);-- if (git_path_root(packname) < 0) {- giterr_set(GITERR_INDEXER, "Path is not absolute");- return -1;- }-- idx = git__calloc(1, sizeof(git_indexer));- GITERR_CHECK_ALLOC(idx);-- open_pack(&idx->pack, packname);-- if ((error = parse_header(&hdr, idx->pack)) < 0)- goto cleanup;-- idx->nr_objects = ntohl(hdr.hdr_entries);-- /* for now, limit to 2^32 objects */- assert(idx->nr_objects == (size_t)((unsigned int)idx->nr_objects));-- error = git_vector_init(&idx->pack->cache, (unsigned int)idx->nr_objects, cache_cmp);- if (error < 0)- goto cleanup;-- idx->pack->has_cache = 1;- error = git_vector_init(&idx->objects, (unsigned int)idx->nr_objects, objects_cmp);- if (error < 0)- goto cleanup;-- *out = idx;-- return 0;--cleanup:- git_indexer_free(idx);-- return -1;-}--static int index_path(git_buf *path, git_indexer *idx)-{- const char prefix[] = "pack-", suffix[] = ".idx";- size_t slash = (size_t)path->size;-- /* search backwards for '/' */- while (slash > 0 && path->ptr[slash - 1] != '/')- slash--;-- if (git_buf_grow(path, slash + 1 + strlen(prefix) +- GIT_OID_HEXSZ + strlen(suffix) + 1) < 0)- return -1;-- git_buf_truncate(path, slash);- git_buf_puts(path, prefix);- git_oid_fmt(path->ptr + git_buf_len(path), &idx->hash);- path->size += GIT_OID_HEXSZ;- git_buf_puts(path, suffix);-- return git_buf_oom(path) ? -1 : 0;-}--int git_indexer_write(git_indexer *idx)-{- git_mwindow *w = NULL;- int error;- unsigned int i, long_offsets = 0, left;- struct git_pack_idx_header hdr;- git_buf filename = GIT_BUF_INIT;- struct entry *entry;- void *packfile_hash;- git_oid file_hash;- SHA_CTX ctx;-- git_vector_sort(&idx->objects);-- git_buf_sets(&filename, idx->pack->pack_name);- git_buf_truncate(&filename, filename.size - strlen("pack"));- git_buf_puts(&filename, "idx");- if (git_buf_oom(&filename))- return -1;-- error = git_filebuf_open(&idx->file, filename.ptr, GIT_FILEBUF_HASH_CONTENTS);- if (error < 0)- goto cleanup;-- /* Write out the header */- hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);- hdr.idx_version = htonl(2);- error = git_filebuf_write(&idx->file, &hdr, sizeof(hdr));- if (error < 0)- goto cleanup;-- /* Write out the fanout table */- for (i = 0; i < 256; ++i) {- uint32_t n = htonl(idx->fanout[i]);- error = git_filebuf_write(&idx->file, &n, sizeof(n));- if (error < 0)- goto cleanup;- }-- /* Write out the object names (SHA-1 hashes) */- SHA1_Init(&ctx);- git_vector_foreach(&idx->objects, i, entry) {- error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid));- SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ);- if (error < 0)- goto cleanup;- }- SHA1_Final(idx->hash.id, &ctx);-- /* Write out the CRC32 values */- git_vector_foreach(&idx->objects, i, entry) {- error = git_filebuf_write(&idx->file, &entry->crc, sizeof(uint32_t));- if (error < 0)- goto cleanup;- }-- /* Write out the offsets */- git_vector_foreach(&idx->objects, i, entry) {- uint32_t n;-- if (entry->offset == UINT32_MAX)- n = htonl(0x80000000 | long_offsets++);- else- n = htonl(entry->offset);-- error = git_filebuf_write(&idx->file, &n, sizeof(uint32_t));- if (error < 0)- goto cleanup;- }-- /* Write out the long offsets */- git_vector_foreach(&idx->objects, i, entry) {- uint32_t split[2];-- if (entry->offset != UINT32_MAX)- continue;-- split[0] = htonl(entry->offset_long >> 32);- split[1] = htonl(entry->offset_long & 0xffffffff);-- error = git_filebuf_write(&idx->file, &split, sizeof(uint32_t) * 2);- if (error < 0)- goto cleanup;- }-- /* Write out the packfile trailer */-- packfile_hash = git_mwindow_open(&idx->pack->mwf, &w, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ, &left);- git_mwindow_close(&w);- if (packfile_hash == NULL) {- error = -1;- goto cleanup;- }-- memcpy(&file_hash, packfile_hash, GIT_OID_RAWSZ);-- git_mwindow_close(&w);-- error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid));- if (error < 0)- goto cleanup;-- /* Write out the index sha */- error = git_filebuf_hash(&file_hash, &idx->file);- if (error < 0)- goto cleanup;-- error = git_filebuf_write(&idx->file, &file_hash, sizeof(git_oid));- if (error < 0)- goto cleanup;-- /* Figure out what the final name should be */- error = index_path(&filename, idx);- if (error < 0)- goto cleanup;-- /* Commit file */- error = git_filebuf_commit_at(&idx->file, filename.ptr, GIT_PACK_FILE_MODE);--cleanup:- git_mwindow_free_all(&idx->pack->mwf);- if (error < 0)- git_filebuf_cleanup(&idx->file);- git_buf_free(&filename);-- return error;-}--int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)-{- git_mwindow_file *mwf;- git_off_t off = sizeof(struct git_pack_header);- int error;- struct entry *entry;- unsigned int left, processed;-- assert(idx && stats);-- mwf = &idx->pack->mwf;- error = git_mwindow_file_register(mwf);- if (error < 0)- return error;-- stats->total = (unsigned int)idx->nr_objects;- stats->processed = processed = 0;-- while (processed < idx->nr_objects) {- git_rawobj obj;- git_oid oid;- struct git_pack_entry *pentry;- git_mwindow *w = NULL;- int i;- git_off_t entry_start = off;- void *packed;- size_t entry_size;- char fmt[GIT_OID_HEXSZ] = {0};-- entry = git__calloc(1, sizeof(*entry));- GITERR_CHECK_ALLOC(entry);-- if (off > UINT31_MAX) {- entry->offset = UINT32_MAX;- entry->offset_long = off;- } else {- entry->offset = (uint32_t)off;- }-- error = git_packfile_unpack(&obj, idx->pack, &off);- if (error < 0)- goto cleanup;-- /* FIXME: Parse the object instead of hashing it */- error = git_odb__hashobj(&oid, &obj);- if (error < 0) {- giterr_set(GITERR_INDEXER, "Failed to hash object");- goto cleanup;- }-- pentry = git__malloc(sizeof(struct git_pack_entry));- if (pentry == NULL) {- error = -1;- goto cleanup;- }-- git_oid_cpy(&pentry->sha1, &oid);- pentry->offset = entry_start;- git_oid_fmt(fmt, &oid);- printf("adding %s to cache\n", fmt);- error = git_vector_insert(&idx->pack->cache, pentry);- if (error < 0)- goto cleanup;-- git_oid_cpy(&entry->oid, &oid);- entry->crc = crc32(0L, Z_NULL, 0);-- entry_size = (size_t)(off - entry_start);- packed = git_mwindow_open(mwf, &w, entry_start, entry_size, &left);- if (packed == NULL) {- error = -1;- goto cleanup;- }- entry->crc = htonl(crc32(entry->crc, packed, (uInt)entry_size));- git_mwindow_close(&w);-- /* Add the object to the list */- error = git_vector_insert(&idx->objects, entry);- if (error < 0)- goto cleanup;-- for (i = oid.id[0]; i < 256; ++i) {- idx->fanout[i]++;+ if (idx->pack) {+ for (k = kh_begin(idx->pack->idx_cache); k != kh_end(idx->pack->idx_cache); k++) {+ if (kh_exist(idx->pack->idx_cache, k))+ git__free(kh_value(idx->pack->idx_cache, k)); } - git__free(obj.data);-- stats->processed = ++processed;+ git_oidmap_free(idx->pack->idx_cache); } -cleanup:- git_mwindow_free_all(mwf);-- return error;--}--void git_indexer_free(git_indexer *idx)-{- unsigned int i;- struct entry *e;- struct git_pack_entry *pe;-- if (idx == NULL)- return;-- p_close(idx->pack->mwf.fd);- git_vector_foreach(&idx->objects, i, e)- git__free(e);- git_vector_free(&idx->objects);- git_vector_foreach(&idx->pack->cache, i, pe)- git__free(pe);- git_vector_free(&idx->pack->cache);- git__free(idx->pack);+ git_vector_foreach(&idx->deltas, i, delta)+ git__free(delta);+ git_vector_free(&idx->deltas);+ git_packfile_free(idx->pack);+ git_filebuf_cleanup(&idx->pack_file); git__free(idx); }-
libgit2/src/iterator.c view
@@ -1,748 +1,1349 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#include "iterator.h"-#include "tree.h"-#include "ignore.h"-#include "buffer.h"-#include "git2/submodule.h"--#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC) do { \- (P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \- GITERR_CHECK_ALLOC(P); \- (P)->base.type = GIT_ITERATOR_ ## NAME_UC; \- (P)->base.start = start ? git__strdup(start) : NULL; \- (P)->base.end = end ? git__strdup(end) : NULL; \- (P)->base.current = NAME_LC ## _iterator__current; \- (P)->base.at_end = NAME_LC ## _iterator__at_end; \- (P)->base.advance = NAME_LC ## _iterator__advance; \- (P)->base.seek = NAME_LC ## _iterator__seek; \- (P)->base.reset = NAME_LC ## _iterator__reset; \- (P)->base.free = NAME_LC ## _iterator__free; \- if ((start && !(P)->base.start) || (end && !(P)->base.end)) \- return -1; \- } while (0)---static int empty_iterator__no_item(- git_iterator *iter, const git_index_entry **entry)-{- GIT_UNUSED(iter);- *entry = NULL;- return 0;-}--static int empty_iterator__at_end(git_iterator *iter)-{- GIT_UNUSED(iter);- return 1;-}--static int empty_iterator__noop(git_iterator *iter)-{- GIT_UNUSED(iter);- return 0;-}--static int empty_iterator__seek(git_iterator *iter, const char *prefix)-{- GIT_UNUSED(iter);- GIT_UNUSED(prefix);- return -1;-}--static void empty_iterator__free(git_iterator *iter)-{- GIT_UNUSED(iter);-}--int git_iterator_for_nothing(git_iterator **iter)-{- git_iterator *i = git__calloc(1, sizeof(git_iterator));- GITERR_CHECK_ALLOC(i);-- i->type = GIT_ITERATOR_EMPTY;- i->current = empty_iterator__no_item;- i->at_end = empty_iterator__at_end;- i->advance = empty_iterator__no_item;- i->seek = empty_iterator__seek;- i->reset = empty_iterator__noop;- i->free = empty_iterator__free;-- *iter = i;-- return 0;-}---typedef struct tree_iterator_frame tree_iterator_frame;-struct tree_iterator_frame {- tree_iterator_frame *next;- git_tree *tree;- char *start;- unsigned int index;-};--typedef struct {- git_iterator base;- git_repository *repo;- tree_iterator_frame *stack;- git_index_entry entry;- git_buf path;- bool path_has_filename;-} tree_iterator;--static const git_tree_entry *tree_iterator__tree_entry(tree_iterator *ti)-{- return (ti->stack == NULL) ? NULL :- git_tree_entry_byindex(ti->stack->tree, ti->stack->index);-}--static char *tree_iterator__current_filename(- tree_iterator *ti, const git_tree_entry *te)-{- if (!ti->path_has_filename) {- if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)- return NULL;- ti->path_has_filename = true;- }-- return ti->path.ptr;-}--static void tree_iterator__pop_frame(tree_iterator *ti)-{- tree_iterator_frame *tf = ti->stack;- ti->stack = tf->next;- if (ti->stack != NULL) /* don't free the initial tree */- git_tree_free(tf->tree);- git__free(tf);-}--static int tree_iterator__to_end(tree_iterator *ti)-{- while (ti->stack && ti->stack->next)- tree_iterator__pop_frame(ti);-- if (ti->stack)- ti->stack->index = git_tree_entrycount(ti->stack->tree);-- return 0;-}--static int tree_iterator__current(- git_iterator *self, const git_index_entry **entry)-{- tree_iterator *ti = (tree_iterator *)self;- const git_tree_entry *te = tree_iterator__tree_entry(ti);-- if (entry)- *entry = NULL;-- if (te == NULL)- return 0;-- ti->entry.mode = te->attr;- git_oid_cpy(&ti->entry.oid, &te->oid);-- ti->entry.path = tree_iterator__current_filename(ti, te);- if (ti->entry.path == NULL)- return -1;-- if (ti->base.end && git__prefixcmp(ti->entry.path, ti->base.end) > 0)- return tree_iterator__to_end(ti);-- if (entry)- *entry = &ti->entry;-- return 0;-}--static int tree_iterator__at_end(git_iterator *self)-{- return (tree_iterator__tree_entry((tree_iterator *)self) == NULL);-}--static tree_iterator_frame *tree_iterator__alloc_frame(- git_tree *tree, char *start)-{- tree_iterator_frame *tf = git__calloc(1, sizeof(tree_iterator_frame));- if (!tf)- return NULL;-- tf->tree = tree;-- if (start && *start) {- tf->start = start;- tf->index = git_tree__prefix_position(tree, start);- }-- return tf;-}--static int tree_iterator__expand_tree(tree_iterator *ti)-{- int error;- git_tree *subtree;- const git_tree_entry *te = tree_iterator__tree_entry(ti);- tree_iterator_frame *tf;- char *relpath;-- while (te != NULL && git_tree_entry__is_tree(te)) {- if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)- return -1;-- /* check that we have not passed the range end */- if (ti->base.end != NULL &&- git__prefixcmp(ti->path.ptr, ti->base.end) > 0)- return tree_iterator__to_end(ti);-- if ((error = git_tree_lookup(&subtree, ti->repo, &te->oid)) < 0)- return error;-- relpath = NULL;-- /* apply range start to new frame if relevant */- if (ti->stack->start &&- git__prefixcmp(ti->stack->start, te->filename) == 0)- {- size_t namelen = strlen(te->filename);- if (ti->stack->start[namelen] == '/')- relpath = ti->stack->start + namelen + 1;- }-- if ((tf = tree_iterator__alloc_frame(subtree, relpath)) == NULL)- return -1;-- tf->next = ti->stack;- ti->stack = tf;-- te = tree_iterator__tree_entry(ti);- }-- return 0;-}--static int tree_iterator__advance(- git_iterator *self, const git_index_entry **entry)-{- int error = 0;- tree_iterator *ti = (tree_iterator *)self;- const git_tree_entry *te = NULL;-- if (entry != NULL)- *entry = NULL;-- if (ti->path_has_filename) {- git_buf_rtruncate_at_char(&ti->path, '/');- ti->path_has_filename = false;- }-- while (ti->stack != NULL) {- te = git_tree_entry_byindex(ti->stack->tree, ++ti->stack->index);- if (te != NULL)- break;-- tree_iterator__pop_frame(ti);-- git_buf_rtruncate_at_char(&ti->path, '/');- }-- if (te && git_tree_entry__is_tree(te))- error = tree_iterator__expand_tree(ti);-- if (!error)- error = tree_iterator__current(self, entry);-- return error;-}--static int tree_iterator__seek(git_iterator *self, const char *prefix)-{- GIT_UNUSED(self);- GIT_UNUSED(prefix);- /* pop stack until matches prefix */- /* seek item in current frame matching prefix */- /* push stack which matches prefix */- return -1;-}--static void tree_iterator__free(git_iterator *self)-{- tree_iterator *ti = (tree_iterator *)self;- while (ti->stack != NULL)- tree_iterator__pop_frame(ti);- git_buf_free(&ti->path);-}--static int tree_iterator__reset(git_iterator *self)-{- tree_iterator *ti = (tree_iterator *)self;-- while (ti->stack && ti->stack->next)- tree_iterator__pop_frame(ti);-- if (ti->stack)- ti->stack->index =- git_tree__prefix_position(ti->stack->tree, ti->base.start);-- git_buf_clear(&ti->path);-- return tree_iterator__expand_tree(ti);-}--int git_iterator_for_tree_range(- git_iterator **iter,- git_repository *repo,- git_tree *tree,- const char *start,- const char *end)-{- int error;- tree_iterator *ti;-- if (tree == NULL)- return git_iterator_for_nothing(iter);-- ITERATOR_BASE_INIT(ti, tree, TREE);-- ti->repo = repo;- ti->stack = tree_iterator__alloc_frame(tree, ti->base.start);-- if ((error = tree_iterator__expand_tree(ti)) < 0)- git_iterator_free((git_iterator *)ti);- else- *iter = (git_iterator *)ti;-- return error;-}---typedef struct {- git_iterator base;- git_index *index;- unsigned int current;-} index_iterator;--static int index_iterator__current(- git_iterator *self, const git_index_entry **entry)-{- index_iterator *ii = (index_iterator *)self;- git_index_entry *ie = git_index_get(ii->index, ii->current);-- if (ie != NULL &&- ii->base.end != NULL &&- git__prefixcmp(ie->path, ii->base.end) > 0)- {- ii->current = git_index_entrycount(ii->index);- ie = NULL;- }-- if (entry)- *entry = ie;-- return 0;-}--static int index_iterator__at_end(git_iterator *self)-{- index_iterator *ii = (index_iterator *)self;- return (ii->current >= git_index_entrycount(ii->index));-}--static int index_iterator__advance(- git_iterator *self, const git_index_entry **entry)-{- index_iterator *ii = (index_iterator *)self;-- if (ii->current < git_index_entrycount(ii->index))- ii->current++;-- return index_iterator__current(self, entry);-}--static int index_iterator__seek(git_iterator *self, const char *prefix)-{- GIT_UNUSED(self);- GIT_UNUSED(prefix);- /* find last item before prefix */- return -1;-}--static int index_iterator__reset(git_iterator *self)-{- index_iterator *ii = (index_iterator *)self;- ii->current = 0;- return 0;-}--static void index_iterator__free(git_iterator *self)-{- index_iterator *ii = (index_iterator *)self;- git_index_free(ii->index);- ii->index = NULL;-}--int git_iterator_for_index_range(- git_iterator **iter,- git_repository *repo,- const char *start,- const char *end)-{- int error;- index_iterator *ii;-- ITERATOR_BASE_INIT(ii, index, INDEX);-- if ((error = git_repository_index(&ii->index, repo)) < 0)- git__free(ii);- else {- ii->current = start ? git_index__prefix_position(ii->index, start) : 0;- *iter = (git_iterator *)ii;- }-- return error;-}---typedef struct workdir_iterator_frame workdir_iterator_frame;-struct workdir_iterator_frame {- workdir_iterator_frame *next;- git_vector entries;- unsigned int index;- char *start;-};--typedef struct {- git_iterator base;- git_repository *repo;- size_t root_len;- workdir_iterator_frame *stack;- git_ignores ignores;- git_index_entry entry;- git_buf path;- int is_ignored;-} workdir_iterator;--static workdir_iterator_frame *workdir_iterator__alloc_frame(void)-{- workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));- if (wf == NULL)- return NULL;- if (git_vector_init(&wf->entries, 0, git_path_with_stat_cmp) != 0) {- git__free(wf);- return NULL;- }- return wf;-}--static void workdir_iterator__free_frame(workdir_iterator_frame *wf)-{- unsigned int i;- git_path_with_stat *path;-- git_vector_foreach(&wf->entries, i, path)- git__free(path);- git_vector_free(&wf->entries);- git__free(wf);-}--static int workdir_iterator__update_entry(workdir_iterator *wi);--static int workdir_iterator__entry_cmp(const void *prefix, const void *item)-{- const git_path_with_stat *ps = item;- return git__prefixcmp((const char *)prefix, ps->path);-}--static int workdir_iterator__expand_dir(workdir_iterator *wi)-{- int error;- workdir_iterator_frame *wf = workdir_iterator__alloc_frame();- GITERR_CHECK_ALLOC(wf);-- error = git_path_dirload_with_stat(wi->path.ptr, wi->root_len, &wf->entries);- if (error < 0 || wf->entries.length == 0) {- workdir_iterator__free_frame(wf);- return GIT_ENOTFOUND;- }-- git_vector_sort(&wf->entries);-- if (!wi->stack)- wf->start = wi->base.start;- else if (wi->stack->start &&- git__prefixcmp(wi->stack->start, wi->path.ptr + wi->root_len) == 0)- wf->start = wi->stack->start;-- if (wf->start)- git_vector_bsearch3(- &wf->index, &wf->entries, workdir_iterator__entry_cmp, wf->start);-- wf->next = wi->stack;- wi->stack = wf;-- /* only push new ignores if this is not top level directory */- if (wi->stack->next != NULL) {- ssize_t slash_pos = git_buf_rfind_next(&wi->path, '/');- (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]);- }-- return workdir_iterator__update_entry(wi);-}--static int workdir_iterator__current(- git_iterator *self, const git_index_entry **entry)-{- workdir_iterator *wi = (workdir_iterator *)self;- *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;- return 0;-}--static int workdir_iterator__at_end(git_iterator *self)-{- return (((workdir_iterator *)self)->entry.path == NULL);-}--static int workdir_iterator__advance(- git_iterator *self, const git_index_entry **entry)-{- int error;- workdir_iterator *wi = (workdir_iterator *)self;- workdir_iterator_frame *wf;- git_path_with_stat *next;-- if (entry != NULL)- *entry = NULL;-- if (wi->entry.path == NULL)- return 0;-- while ((wf = wi->stack) != NULL) {- next = git_vector_get(&wf->entries, ++wf->index);- if (next != NULL) {- if (strcmp(next->path, DOT_GIT "/") == 0)- continue;- /* else found a good entry */- break;- }-- /* pop workdir directory stack */- wi->stack = wf->next;- workdir_iterator__free_frame(wf);- git_ignore__pop_dir(&wi->ignores);-- if (wi->stack == NULL) {- memset(&wi->entry, 0, sizeof(wi->entry));- return 0;- }- }-- error = workdir_iterator__update_entry(wi);-- if (!error && entry != NULL)- error = workdir_iterator__current(self, entry);-- return error;-}--static int workdir_iterator__seek(git_iterator *self, const char *prefix)-{- GIT_UNUSED(self);- GIT_UNUSED(prefix);- /* pop stack until matching prefix */- /* find prefix item in current frame */- /* push subdirectories as deep as possible while matching */- return 0;-}--static int workdir_iterator__reset(git_iterator *self)-{- workdir_iterator *wi = (workdir_iterator *)self;- while (wi->stack != NULL && wi->stack->next != NULL) {- workdir_iterator_frame *wf = wi->stack;- wi->stack = wf->next;- workdir_iterator__free_frame(wf);- git_ignore__pop_dir(&wi->ignores);- }- if (wi->stack)- wi->stack->index = 0;- return 0;-}--static void workdir_iterator__free(git_iterator *self)-{- workdir_iterator *wi = (workdir_iterator *)self;-- while (wi->stack != NULL) {- workdir_iterator_frame *wf = wi->stack;- wi->stack = wf->next;- workdir_iterator__free_frame(wf);- }-- git_ignore__free(&wi->ignores);- git_buf_free(&wi->path);-}--static int workdir_iterator__update_entry(workdir_iterator *wi)-{- git_path_with_stat *ps = git_vector_get(&wi->stack->entries, wi->stack->index);-- git_buf_truncate(&wi->path, wi->root_len);- memset(&wi->entry, 0, sizeof(wi->entry));-- if (!ps)- return 0;-- if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0)- return -1;-- if (wi->base.end &&- git__prefixcmp(wi->path.ptr + wi->root_len, wi->base.end) > 0)- return 0;-- wi->entry.path = ps->path;-- /* skip over .git directory */- if (strcmp(ps->path, DOT_GIT "/") == 0)- return workdir_iterator__advance((git_iterator *)wi, NULL);-- /* if there is an error processing the entry, treat as ignored */- wi->is_ignored = 1;-- git_index__init_entry_from_stat(&ps->st, &wi->entry);-- /* need different mode here to keep directories during iteration */- wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);-- /* if this is a file type we don't handle, treat as ignored */- if (wi->entry.mode == 0)- return 0;-- /* okay, we are far enough along to look up real ignore rule */- if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)- return 0; /* if error, ignore it and ignore file */-- /* detect submodules */- if (S_ISDIR(wi->entry.mode)) {- bool is_submodule = git_path_contains(&wi->path, DOT_GIT);-- /* if there is no .git, still check submodules data */- if (!is_submodule) {- int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path);- is_submodule = (res == 0);- if (res == GIT_ENOTFOUND)- giterr_clear();- }-- /* if submodule, mark as GITLINK and remove trailing slash */- if (is_submodule) {- size_t len = strlen(wi->entry.path);- assert(wi->entry.path[len - 1] == '/');- wi->entry.path[len - 1] = '\0';- wi->entry.mode = S_IFGITLINK;- }- }-- return 0;-}--int git_iterator_for_workdir_range(- git_iterator **iter,- git_repository *repo,- const char *start,- const char *end)-{- int error;- workdir_iterator *wi;-- assert(iter && repo);-- if (git_repository_is_bare(repo)) {- giterr_set(GITERR_INVALID,- "Cannot scan working directory for bare repo");- return -1;- }-- ITERATOR_BASE_INIT(wi, workdir, WORKDIR);-- wi->repo = repo;-- if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||- git_path_to_dir(&wi->path) < 0 ||- git_ignore__for_path(repo, "", &wi->ignores) < 0)- {- git__free(wi);- return -1;- }-- wi->root_len = wi->path.size;-- if ((error = workdir_iterator__expand_dir(wi)) < 0) {- if (error == GIT_ENOTFOUND)- error = 0;- else {- git_iterator_free((git_iterator *)wi);- wi = NULL;- }- }-- *iter = (git_iterator *)wi;-- return error;-}---int git_iterator_current_tree_entry(- git_iterator *iter, const git_tree_entry **tree_entry)-{- *tree_entry = (iter->type != GIT_ITERATOR_TREE) ? NULL :- tree_iterator__tree_entry((tree_iterator *)iter);- return 0;-}--int git_iterator_current_is_ignored(git_iterator *iter)-{- return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :- ((workdir_iterator *)iter)->is_ignored;-}--int git_iterator_advance_into_directory(- git_iterator *iter, const git_index_entry **entry)-{- workdir_iterator *wi = (workdir_iterator *)iter;-- if (iter->type == GIT_ITERATOR_WORKDIR &&- wi->entry.path &&- S_ISDIR(wi->entry.mode) &&- !S_ISGITLINK(wi->entry.mode))- {- if (workdir_iterator__expand_dir(wi) < 0)- /* if error loading or if empty, skip the directory. */- return workdir_iterator__advance(iter, entry);- }-- return entry ? git_iterator_current(iter, entry) : 0;-}--int git_iterator_cmp(- git_iterator *iter, const char *path_prefix)-{- const git_index_entry *entry;-- /* a "done" iterator is after every prefix */- if (git_iterator_current(iter, &entry) < 0 ||- entry == NULL)- return 1;-- /* a NULL prefix is after any valid iterator */- if (!path_prefix)- return -1;-- return git__prefixcmp(entry->path, path_prefix);-}-+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "iterator.h"+#include "tree.h"+#include "ignore.h"+#include "buffer.h"+#include "git2/submodule.h"+#include <ctype.h>++#define ITERATOR_SET_CB(P,NAME_LC) do { \+ (P)->cb.current = NAME_LC ## _iterator__current; \+ (P)->cb.advance = NAME_LC ## _iterator__advance; \+ (P)->cb.advance_into = NAME_LC ## _iterator__advance_into; \+ (P)->cb.seek = NAME_LC ## _iterator__seek; \+ (P)->cb.reset = NAME_LC ## _iterator__reset; \+ (P)->cb.at_end = NAME_LC ## _iterator__at_end; \+ (P)->cb.free = NAME_LC ## _iterator__free; \+ } while (0)++#define ITERATOR_CASE_FLAGS \+ (GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_IGNORE_CASE)++#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC,REPO) do { \+ (P) = git__calloc(1, sizeof(NAME_LC ## _iterator)); \+ GITERR_CHECK_ALLOC(P); \+ (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \+ (P)->base.cb = &(P)->cb; \+ ITERATOR_SET_CB(P,NAME_LC); \+ (P)->base.repo = (REPO); \+ (P)->base.start = start ? git__strdup(start) : NULL; \+ (P)->base.end = end ? git__strdup(end) : NULL; \+ if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \+ git__free(P); return -1; } \+ (P)->base.prefixcomp = git__prefixcmp; \+ (P)->base.flags = flags & ~ITERATOR_CASE_FLAGS; \+ if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \+ (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \+ } while (0)++#define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)+#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE)+#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES)+#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND)+#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND)++#define iterator__end(I) ((git_iterator *)(I))->end+#define iterator__past_end(I,PATH) \+ (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0)+++static int iterator__reset_range(+ git_iterator *iter, const char *start, const char *end)+{+ if (start) {+ if (iter->start)+ git__free(iter->start);+ iter->start = git__strdup(start);+ GITERR_CHECK_ALLOC(iter->start);+ }++ if (end) {+ if (iter->end)+ git__free(iter->end);+ iter->end = git__strdup(end);+ GITERR_CHECK_ALLOC(iter->end);+ }++ return 0;+}++static int iterator__update_ignore_case(+ git_iterator *iter,+ git_iterator_flag_t flags)+{+ int error = 0, ignore_case = -1;++ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)+ ignore_case = true;+ else if ((flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0)+ ignore_case = false;+ else {+ git_index *index;++ if (!(error = git_repository_index__weakptr(&index, iter->repo)))+ ignore_case = (index->ignore_case != false);+ }++ if (ignore_case > 0)+ iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE);+ else if (ignore_case == 0)+ iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE);++ iter->prefixcomp = iterator__ignore_case(iter) ?+ git__prefixcmp_icase : git__prefixcmp;++ return error;+}++GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry)+{+ if (entry) *entry = NULL;+}+++static int empty_iterator__noop(const git_index_entry **e, git_iterator *i)+{+ GIT_UNUSED(i);+ iterator__clear_entry(e);+ return 0;+}++static int empty_iterator__seek(git_iterator *i, const char *p)+{+ GIT_UNUSED(i); GIT_UNUSED(p);+ return -1;+}++static int empty_iterator__reset(git_iterator *i, const char *s, const char *e)+{+ GIT_UNUSED(i); GIT_UNUSED(s); GIT_UNUSED(e);+ return 0;+}++static int empty_iterator__at_end(git_iterator *i)+{+ GIT_UNUSED(i);+ return 1;+}++static void empty_iterator__free(git_iterator *i)+{+ GIT_UNUSED(i);+}++typedef struct {+ git_iterator base;+ git_iterator_callbacks cb;+} empty_iterator;++int git_iterator_for_nothing(+ git_iterator **iter,+ git_iterator_flag_t flags,+ const char *start,+ const char *end)+{+ empty_iterator *i;++#define empty_iterator__current empty_iterator__noop+#define empty_iterator__advance empty_iterator__noop+#define empty_iterator__advance_into empty_iterator__noop++ ITERATOR_BASE_INIT(i, empty, EMPTY, NULL);++ if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)+ i->base.flags |= GIT_ITERATOR_IGNORE_CASE;++ *iter = (git_iterator *)i;+ return 0;+}+++typedef struct tree_iterator_entry tree_iterator_entry;+struct tree_iterator_entry {+ tree_iterator_entry *parent;+ const git_tree_entry *te;+ git_tree *tree;+};++typedef struct tree_iterator_frame tree_iterator_frame;+struct tree_iterator_frame {+ tree_iterator_frame *up, *down;++ size_t n_entries; /* items in this frame */+ size_t current; /* start of currently active range in frame */+ size_t next; /* start of next range in frame */++ const char *start;+ size_t startlen;++ tree_iterator_entry *entries[GIT_FLEX_ARRAY];+};++typedef struct {+ git_iterator base;+ git_iterator_callbacks cb;+ tree_iterator_frame *head, *root;+ git_pool pool;+ git_index_entry entry;+ git_buf path;+ int path_ambiguities;+ bool path_has_filename;+ int (*strncomp)(const char *a, const char *b, size_t sz);+} tree_iterator;++static char *tree_iterator__current_filename(+ tree_iterator *ti, const git_tree_entry *te)+{+ if (!ti->path_has_filename) {+ if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0)+ return NULL;++ if (git_tree_entry__is_tree(te) && git_buf_putc(&ti->path, '/') < 0)+ return NULL;++ ti->path_has_filename = true;+ }++ return ti->path.ptr;+}++static void tree_iterator__rewrite_filename(tree_iterator *ti)+{+ tree_iterator_entry *scan = ti->head->entries[ti->head->current];+ ssize_t strpos = ti->path.size;+ const git_tree_entry *te;++ if (strpos && ti->path.ptr[strpos - 1] == '/')+ strpos--;++ for (; scan && (te = scan->te); scan = scan->parent) {+ strpos -= te->filename_len;+ memcpy(&ti->path.ptr[strpos], te->filename, te->filename_len);+ strpos -= 1; /* separator */+ }+}++static int tree_iterator__te_cmp(+ const git_tree_entry *a,+ const git_tree_entry *b,+ int (*compare)(const char *, const char *, size_t))+{+ return git_path_cmp(+ a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE,+ b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE,+ compare);+}++static int tree_iterator__ci_cmp(const void *a, const void *b, void *p)+{+ const tree_iterator_entry *ae = a, *be = b;+ int cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncasecmp);++ if (!cmp) {+ /* stabilize sort order among equivalent names */+ if (!ae->parent->te || !be->parent->te)+ cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncmp);+ else+ cmp = tree_iterator__ci_cmp(ae->parent, be->parent, p);+ }++ return cmp;+}++static int tree_iterator__search_cmp(const void *key, const void *val, void *p)+{+ const tree_iterator_frame *tf = key;+ const git_tree_entry *te = ((tree_iterator_entry *)val)->te;++ return git_path_cmp(+ tf->start, tf->startlen, false,+ te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE,+ ((tree_iterator *)p)->strncomp);+}++static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)+{+ int error;+ const git_tree_entry *te, *last = NULL;++ tf->next = tf->current;++ for (; tf->next < tf->n_entries; tf->next++, last = te) {+ te = tf->entries[tf->next]->te;++ if (last && tree_iterator__te_cmp(last, te, ti->strncomp))+ break;++ /* load trees for items in [current,next) range */+ if (git_tree_entry__is_tree(te) &&+ (error = git_tree_lookup(+ &tf->entries[tf->next]->tree, ti->base.repo, &te->oid)) < 0)+ return error;+ }++ if (tf->next > tf->current + 1)+ ti->path_ambiguities++;++ if (last && !tree_iterator__current_filename(ti, last))+ return -1;++ return 0;+}++GIT_INLINE(bool) tree_iterator__at_tree(tree_iterator *ti)+{+ return (ti->head->current < ti->head->n_entries &&+ ti->head->entries[ti->head->current]->tree != NULL);+}++static int tree_iterator__push_frame(tree_iterator *ti)+{+ int error = 0;+ tree_iterator_frame *head = ti->head, *tf = NULL;+ size_t i, n_entries = 0;++ if (head->current >= head->n_entries || !head->entries[head->current]->tree)+ return 0;++ for (i = head->current; i < head->next; ++i)+ n_entries += git_tree_entrycount(head->entries[i]->tree);++ tf = git__calloc(sizeof(tree_iterator_frame) ++ n_entries * sizeof(tree_iterator_entry *), 1);+ GITERR_CHECK_ALLOC(tf);++ tf->n_entries = n_entries;++ tf->up = head;+ head->down = tf;+ ti->head = tf;++ for (i = head->current, n_entries = 0; i < head->next; ++i) {+ git_tree *tree = head->entries[i]->tree;+ size_t j, max_j = git_tree_entrycount(tree);++ for (j = 0; j < max_j; ++j) {+ tree_iterator_entry *entry = git_pool_malloc(&ti->pool, 1);+ GITERR_CHECK_ALLOC(entry);++ entry->parent = head->entries[i];+ entry->te = git_tree_entry_byindex(tree, j);+ entry->tree = NULL;++ tf->entries[n_entries++] = entry;+ }+ }++ /* if ignore_case, sort entries case insensitively */+ if (iterator__ignore_case(ti))+ git__tsort_r(+ (void **)tf->entries, tf->n_entries, tree_iterator__ci_cmp, tf);++ /* pick tf->current based on "start" (or start at zero) */+ if (head->startlen > 0) {+ git__bsearch_r((void **)tf->entries, tf->n_entries, head,+ tree_iterator__search_cmp, ti, &tf->current);++ while (tf->current &&+ !tree_iterator__search_cmp(head, tf->entries[tf->current-1], ti))+ tf->current--;++ if ((tf->start = strchr(head->start, '/')) != NULL) {+ tf->start++;+ tf->startlen = strlen(tf->start);+ }+ }++ ti->path_has_filename = false;++ if ((error = tree_iterator__set_next(ti, tf)) < 0)+ return error;++ /* autoexpand as needed */+ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))+ return tree_iterator__push_frame(ti);++ return 0;+}++static bool tree_iterator__move_to_next(+ tree_iterator *ti, tree_iterator_frame *tf)+{+ if (tf->next > tf->current + 1)+ ti->path_ambiguities--;++ if (!tf->up) { /* at root */+ tf->current = tf->next;+ return false;+ }++ for (; tf->current < tf->next; tf->current++) {+ git_tree_free(tf->entries[tf->current]->tree);+ tf->entries[tf->current]->tree = NULL;+ }++ return (tf->current < tf->n_entries);+}++static bool tree_iterator__pop_frame(tree_iterator *ti, bool final)+{+ tree_iterator_frame *tf = ti->head;++ if (!tf->up)+ return false;++ ti->head = tf->up;+ ti->head->down = NULL;++ tree_iterator__move_to_next(ti, tf);++ if (!final) { /* if final, don't bother to clean up */+ git_pool_free_array(&ti->pool, tf->n_entries, (void **)tf->entries);+ git_buf_rtruncate_at_char(&ti->path, '/');+ }++ git__free(tf);++ return true;+}++static int tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final)+{+ while (tree_iterator__pop_frame(ti, final)) /* pop to root */;++ if (!final) {+ ti->head->current = to_end ? ti->head->n_entries : 0;+ ti->path_ambiguities = 0;+ git_buf_clear(&ti->path);+ }++ return 0;+}++static int tree_iterator__current(+ const git_index_entry **entry, git_iterator *self)+{+ tree_iterator *ti = (tree_iterator *)self;+ tree_iterator_frame *tf = ti->head;+ const git_tree_entry *te;++ iterator__clear_entry(entry);++ if (tf->current >= tf->n_entries)+ return 0;+ te = tf->entries[tf->current]->te;++ ti->entry.mode = te->attr;+ git_oid_cpy(&ti->entry.oid, &te->oid);++ ti->entry.path = tree_iterator__current_filename(ti, te);+ GITERR_CHECK_ALLOC(ti->entry.path);++ if (ti->path_ambiguities > 0)+ tree_iterator__rewrite_filename(ti);++ if (iterator__past_end(ti, ti->entry.path))+ return tree_iterator__pop_all(ti, true, false);++ if (entry)+ *entry = &ti->entry;++ return 0;+}++static int tree_iterator__advance_into(+ const git_index_entry **entry, git_iterator *self)+{+ int error = 0;+ tree_iterator *ti = (tree_iterator *)self;++ iterator__clear_entry(entry);++ if (tree_iterator__at_tree(ti) &&+ !(error = tree_iterator__push_frame(ti)))+ error = tree_iterator__current(entry, self);++ return error;+}++static int tree_iterator__advance(+ const git_index_entry **entry, git_iterator *self)+{+ int error;+ tree_iterator *ti = (tree_iterator *)self;+ tree_iterator_frame *tf = ti->head;++ iterator__clear_entry(entry);++ if (tf->current > tf->n_entries)+ return 0;++ if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) &&+ tree_iterator__at_tree(ti))+ return tree_iterator__advance_into(entry, self);++ if (ti->path_has_filename) {+ git_buf_rtruncate_at_char(&ti->path, '/');+ ti->path_has_filename = false;+ }++ /* scan forward and up, advancing in frame or popping frame when done */+ while (!tree_iterator__move_to_next(ti, tf) &&+ tree_iterator__pop_frame(ti, false))+ tf = ti->head;++ /* find next and load trees */+ if ((error = tree_iterator__set_next(ti, tf)) < 0)+ return error;++ /* deal with include_trees / auto_expand as needed */+ if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))+ return tree_iterator__advance_into(entry, self);++ return tree_iterator__current(entry, self);+}++static int tree_iterator__seek(git_iterator *self, const char *prefix)+{+ GIT_UNUSED(self); GIT_UNUSED(prefix);+ return -1;+}++static int tree_iterator__reset(+ git_iterator *self, const char *start, const char *end)+{+ tree_iterator *ti = (tree_iterator *)self;++ tree_iterator__pop_all(ti, false, false);++ if (iterator__reset_range(self, start, end) < 0)+ return -1;++ return tree_iterator__push_frame(ti); /* re-expand root tree */+}++static int tree_iterator__at_end(git_iterator *self)+{+ tree_iterator *ti = (tree_iterator *)self;+ return (ti->head->current >= ti->head->n_entries);+}++static void tree_iterator__free(git_iterator *self)+{+ tree_iterator *ti = (tree_iterator *)self;++ tree_iterator__pop_all(ti, true, false);++ git_tree_free(ti->head->entries[0]->tree);+ git__free(ti->head);+ git_pool_clear(&ti->pool);+ git_buf_free(&ti->path);+}++static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree)+{+ size_t sz = sizeof(tree_iterator_frame) + sizeof(tree_iterator_entry);+ tree_iterator_frame *root = git__calloc(sz, sizeof(char));+ GITERR_CHECK_ALLOC(root);++ root->n_entries = 1;+ root->next = 1;+ root->start = ti->base.start;+ root->startlen = root->start ? strlen(root->start) : 0;+ root->entries[0] = git_pool_mallocz(&ti->pool, 1);+ GITERR_CHECK_ALLOC(root->entries[0]);+ root->entries[0]->tree = tree;++ ti->head = ti->root = root;++ return 0;+}++int git_iterator_for_tree(+ git_iterator **iter,+ git_tree *tree,+ git_iterator_flag_t flags,+ const char *start,+ const char *end)+{+ int error;+ tree_iterator *ti;++ if (tree == NULL)+ return git_iterator_for_nothing(iter, flags, start, end);++ if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0)+ return error;++ ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree));++ if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0)+ goto fail;+ ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp;++ if ((error = git_pool_init(&ti->pool, sizeof(tree_iterator_entry),0)) < 0 ||+ (error = tree_iterator__create_root_frame(ti, tree)) < 0 ||+ (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */+ goto fail;++ *iter = (git_iterator *)ti;+ return 0;++fail:+ git_iterator_free((git_iterator *)ti);+ return error;+}+++typedef struct {+ git_iterator base;+ git_iterator_callbacks cb;+ git_index *index;+ size_t current;+ /* when not in autoexpand mode, use these to represent "tree" state */+ git_buf partial;+ size_t partial_pos;+ char restore_terminator;+ git_index_entry tree_entry;+} index_iterator;++static const git_index_entry *index_iterator__index_entry(index_iterator *ii)+{+ const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);++ if (ie != NULL && iterator__past_end(ii, ie->path)) {+ ii->current = git_index_entrycount(ii->index);+ ie = NULL;+ }++ return ie;+}++static const git_index_entry *index_iterator__skip_conflicts(index_iterator *ii)+{+ const git_index_entry *ie;++ while ((ie = index_iterator__index_entry(ii)) != NULL &&+ git_index_entry_stage(ie) != 0)+ ii->current++;++ return ie;+}++static void index_iterator__next_prefix_tree(index_iterator *ii)+{+ const char *slash;++ if (!iterator__include_trees(ii))+ return;++ slash = strchr(&ii->partial.ptr[ii->partial_pos], '/');++ if (slash != NULL) {+ ii->partial_pos = (slash - ii->partial.ptr) + 1;+ ii->restore_terminator = ii->partial.ptr[ii->partial_pos];+ ii->partial.ptr[ii->partial_pos] = '\0';+ } else {+ ii->partial_pos = ii->partial.size;+ }++ if (index_iterator__index_entry(ii) == NULL)+ ii->partial_pos = ii->partial.size;+}++static int index_iterator__first_prefix_tree(index_iterator *ii)+{+ const git_index_entry *ie = index_iterator__skip_conflicts(ii);+ const char *scan, *prior, *slash;++ if (!ie || !iterator__include_trees(ii))+ return 0;++ /* find longest common prefix with prior index entry */+ for (scan = slash = ie->path, prior = ii->partial.ptr;+ *scan && *scan == *prior; ++scan, ++prior)+ if (*scan == '/')+ slash = scan;++ if (git_buf_sets(&ii->partial, ie->path) < 0)+ return -1;++ ii->partial_pos = (slash - ie->path) + 1;+ index_iterator__next_prefix_tree(ii);++ return 0;+}++#define index_iterator__at_tree(I) \+ (iterator__include_trees(I) && (I)->partial_pos < (I)->partial.size)++static int index_iterator__current(+ const git_index_entry **entry, git_iterator *self)+{+ index_iterator *ii = (index_iterator *)self;+ const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);++ if (ie != NULL && index_iterator__at_tree(ii)) {+ ii->tree_entry.path = ii->partial.ptr;+ ie = &ii->tree_entry;+ }++ if (entry)+ *entry = ie;++ return 0;+}++static int index_iterator__at_end(git_iterator *self)+{+ index_iterator *ii = (index_iterator *)self;+ return (ii->current >= git_index_entrycount(ii->index));+}++static int index_iterator__advance(+ const git_index_entry **entry, git_iterator *self)+{+ index_iterator *ii = (index_iterator *)self;+ size_t entrycount = git_index_entrycount(ii->index);+ const git_index_entry *ie;++ if (index_iterator__at_tree(ii)) {+ if (iterator__do_autoexpand(ii)) {+ ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;+ index_iterator__next_prefix_tree(ii);+ } else {+ /* advance to sibling tree (i.e. find entry with new prefix) */+ while (ii->current < entrycount) {+ ii->current++;++ if (!(ie = git_index_get_byindex(ii->index, ii->current)) ||+ ii->base.prefixcomp(ie->path, ii->partial.ptr) != 0)+ break;+ }++ if (index_iterator__first_prefix_tree(ii) < 0)+ return -1;+ }+ } else {+ if (ii->current < entrycount)+ ii->current++;++ if (index_iterator__first_prefix_tree(ii) < 0)+ return -1;+ }++ return index_iterator__current(entry, self);+}++static int index_iterator__advance_into(+ const git_index_entry **entry, git_iterator *self)+{+ index_iterator *ii = (index_iterator *)self;+ const git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);++ if (ie != NULL && index_iterator__at_tree(ii)) {+ if (ii->restore_terminator)+ ii->partial.ptr[ii->partial_pos] = ii->restore_terminator;+ index_iterator__next_prefix_tree(ii);+ }++ return index_iterator__current(entry, self);+}++static int index_iterator__seek(git_iterator *self, const char *prefix)+{+ GIT_UNUSED(self); GIT_UNUSED(prefix);+ return -1;+}++static int index_iterator__reset(+ git_iterator *self, const char *start, const char *end)+{+ index_iterator *ii = (index_iterator *)self;+ const git_index_entry *ie;++ if (iterator__reset_range(self, start, end) < 0)+ return -1;++ ii->current = ii->base.start ?+ git_index__prefix_position(ii->index, ii->base.start) : 0;++ if ((ie = index_iterator__skip_conflicts(ii)) == NULL)+ return 0;++ if (git_buf_sets(&ii->partial, ie->path) < 0)+ return -1;++ ii->partial_pos = 0;++ if (ii->base.start) {+ size_t startlen = strlen(ii->base.start);++ ii->partial_pos = (startlen > ii->partial.size) ?+ ii->partial.size : startlen;+ }++ index_iterator__next_prefix_tree(ii);++ return 0;+}++static void index_iterator__free(git_iterator *self)+{+ index_iterator *ii = (index_iterator *)self;+ git_index_free(ii->index);+ ii->index = NULL;++ git_buf_free(&ii->partial);+}++int git_iterator_for_index(+ git_iterator **iter,+ git_index *index,+ git_iterator_flag_t flags,+ const char *start,+ const char *end)+{+ index_iterator *ii;++ ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index));++ if (index->ignore_case) {+ ii->base.flags |= GIT_ITERATOR_IGNORE_CASE;+ ii->base.prefixcomp = git__prefixcmp_icase;+ }++ ii->index = index;+ GIT_REFCOUNT_INC(index);++ git_buf_init(&ii->partial, 0);+ ii->tree_entry.mode = GIT_FILEMODE_TREE;++ index_iterator__reset((git_iterator *)ii, NULL, NULL);++ *iter = (git_iterator *)ii;++ return 0;+}+++#define WORKDIR_MAX_DEPTH 100++typedef struct workdir_iterator_frame workdir_iterator_frame;+struct workdir_iterator_frame {+ workdir_iterator_frame *next;+ git_vector entries;+ size_t index;+};++typedef struct {+ git_iterator base;+ git_iterator_callbacks cb;+ workdir_iterator_frame *stack;+ git_ignores ignores;+ git_index_entry entry;+ git_buf path;+ size_t root_len;+ int is_ignored;+ int depth;+} workdir_iterator;++GIT_INLINE(bool) path_is_dotgit(const git_path_with_stat *ps)+{+ if (!ps)+ return false;+ else {+ const char *path = ps->path;+ size_t len = ps->path_len;++ if (len < 4)+ return false;+ if (path[len - 1] == '/')+ len--;+ if (tolower(path[len - 1]) != 't' ||+ tolower(path[len - 2]) != 'i' ||+ tolower(path[len - 3]) != 'g' ||+ tolower(path[len - 4]) != '.')+ return false;+ return (len == 4 || path[len - 5] == '/');+ }+}++static workdir_iterator_frame *workdir_iterator__alloc_frame(+ workdir_iterator *wi)+{+ workdir_iterator_frame *wf = git__calloc(1, sizeof(workdir_iterator_frame));+ git_vector_cmp entry_compare = CASESELECT(+ iterator__ignore_case(wi),+ git_path_with_stat_cmp_icase, git_path_with_stat_cmp);++ if (wf == NULL)+ return NULL;++ if (git_vector_init(&wf->entries, 0, entry_compare) != 0) {+ git__free(wf);+ return NULL;+ }++ return wf;+}++static void workdir_iterator__free_frame(workdir_iterator_frame *wf)+{+ unsigned int i;+ git_path_with_stat *path;++ git_vector_foreach(&wf->entries, i, path)+ git__free(path);+ git_vector_free(&wf->entries);+ git__free(wf);+}++static int workdir_iterator__update_entry(workdir_iterator *wi);++static int workdir_iterator__entry_cmp(const void *i, const void *item)+{+ const workdir_iterator *wi = (const workdir_iterator *)i;+ const git_path_with_stat *ps = item;+ return wi->base.prefixcomp(wi->base.start, ps->path);+}++static void workdir_iterator__seek_frame_start(+ workdir_iterator *wi, workdir_iterator_frame *wf)+{+ if (!wf)+ return;++ if (wi->base.start)+ git_vector_bsearch2(+ &wf->index, &wf->entries, workdir_iterator__entry_cmp, wi);+ else+ wf->index = 0;++ if (path_is_dotgit(git_vector_get(&wf->entries, wf->index)))+ wf->index++;+}++static int workdir_iterator__expand_dir(workdir_iterator *wi)+{+ int error;+ workdir_iterator_frame *wf;++ wf = workdir_iterator__alloc_frame(wi);+ GITERR_CHECK_ALLOC(wf);++ error = git_path_dirload_with_stat(+ wi->path.ptr, wi->root_len, iterator__ignore_case(wi),+ wi->base.start, wi->base.end, &wf->entries);++ if (error < 0 || wf->entries.length == 0) {+ workdir_iterator__free_frame(wf);+ return GIT_ENOTFOUND;+ }++ if (++(wi->depth) > WORKDIR_MAX_DEPTH) {+ giterr_set(GITERR_REPOSITORY,+ "Working directory is too deep (%d)", wi->depth);+ workdir_iterator__free_frame(wf);+ return -1;+ }++ workdir_iterator__seek_frame_start(wi, wf);++ /* only push new ignores if this is not top level directory */+ if (wi->stack != NULL) {+ ssize_t slash_pos = git_buf_rfind_next(&wi->path, '/');+ (void)git_ignore__push_dir(&wi->ignores, &wi->path.ptr[slash_pos + 1]);+ }++ wf->next = wi->stack;+ wi->stack = wf;++ return workdir_iterator__update_entry(wi);+}++static int workdir_iterator__current(+ const git_index_entry **entry, git_iterator *self)+{+ workdir_iterator *wi = (workdir_iterator *)self;+ if (entry)+ *entry = (wi->entry.path == NULL) ? NULL : &wi->entry;+ return 0;+}++static int workdir_iterator__at_end(git_iterator *self)+{+ return (((workdir_iterator *)self)->entry.path == NULL);+}++static int workdir_iterator__advance_into(+ const git_index_entry **entry, git_iterator *iter)+{+ int error = 0;+ workdir_iterator *wi = (workdir_iterator *)iter;++ iterator__clear_entry(entry);++ /* workdir iterator will allow you to explicitly advance into a+ * commit/submodule (as well as a tree) to avoid some cases where an+ * entry is mislabeled as a submodule in the working directory+ */+ if (wi->entry.path != NULL &&+ (wi->entry.mode == GIT_FILEMODE_TREE ||+ wi->entry.mode == GIT_FILEMODE_COMMIT))+ /* returns GIT_ENOTFOUND if the directory is empty */+ error = workdir_iterator__expand_dir(wi);++ if (!error && entry)+ error = workdir_iterator__current(entry, iter);++ return error;+}++static int workdir_iterator__advance(+ const git_index_entry **entry, git_iterator *self)+{+ int error = 0;+ workdir_iterator *wi = (workdir_iterator *)self;+ workdir_iterator_frame *wf;+ git_path_with_stat *next;++ /* given include_trees & autoexpand, we might have to go into a tree */+ if (iterator__do_autoexpand(wi) &&+ wi->entry.path != NULL &&+ wi->entry.mode == GIT_FILEMODE_TREE)+ {+ error = workdir_iterator__advance_into(entry, self);++ /* continue silently past empty directories if autoexpanding */+ if (error != GIT_ENOTFOUND)+ return error;+ giterr_clear();+ error = 0;+ }++ if (entry != NULL)+ *entry = NULL;++ while (wi->entry.path != NULL) {+ wf = wi->stack;+ next = git_vector_get(&wf->entries, ++wf->index);++ if (next != NULL) {+ /* match git's behavior of ignoring anything named ".git" */+ if (path_is_dotgit(next))+ continue;+ /* else found a good entry */+ break;+ }++ /* pop stack if anything is left to pop */+ if (!wf->next) {+ memset(&wi->entry, 0, sizeof(wi->entry));+ return 0;+ }++ wi->stack = wf->next;+ wi->depth--;+ workdir_iterator__free_frame(wf);+ git_ignore__pop_dir(&wi->ignores);+ }++ error = workdir_iterator__update_entry(wi);++ if (!error && entry != NULL)+ error = workdir_iterator__current(entry, self);++ return error;+}++static int workdir_iterator__seek(git_iterator *self, const char *prefix)+{+ GIT_UNUSED(self);+ GIT_UNUSED(prefix);+ /* pop stack until matching prefix */+ /* find prefix item in current frame */+ /* push subdirectories as deep as possible while matching */+ return 0;+}++static int workdir_iterator__reset(+ git_iterator *self, const char *start, const char *end)+{+ workdir_iterator *wi = (workdir_iterator *)self;++ while (wi->stack != NULL && wi->stack->next != NULL) {+ workdir_iterator_frame *wf = wi->stack;+ wi->stack = wf->next;+ workdir_iterator__free_frame(wf);+ git_ignore__pop_dir(&wi->ignores);+ }+ wi->depth = 0;++ if (iterator__reset_range(self, start, end) < 0)+ return -1;++ workdir_iterator__seek_frame_start(wi, wi->stack);++ return workdir_iterator__update_entry(wi);+}++static void workdir_iterator__free(git_iterator *self)+{+ workdir_iterator *wi = (workdir_iterator *)self;++ while (wi->stack != NULL) {+ workdir_iterator_frame *wf = wi->stack;+ wi->stack = wf->next;+ workdir_iterator__free_frame(wf);+ }++ git_ignore__free(&wi->ignores);+ git_buf_free(&wi->path);+}++static int workdir_iterator__update_entry(workdir_iterator *wi)+{+ int error = 0;+ git_path_with_stat *ps =+ git_vector_get(&wi->stack->entries, wi->stack->index);++ git_buf_truncate(&wi->path, wi->root_len);+ memset(&wi->entry, 0, sizeof(wi->entry));++ if (!ps)+ return 0;++ /* skip over .git entries */+ if (path_is_dotgit(ps))+ return workdir_iterator__advance(NULL, (git_iterator *)wi);++ if (git_buf_put(&wi->path, ps->path, ps->path_len) < 0)+ return -1;++ if (iterator__past_end(wi, wi->path.ptr + wi->root_len))+ return 0;++ wi->entry.path = ps->path;++ wi->is_ignored = -1;++ git_index_entry__init_from_stat(&wi->entry, &ps->st);++ /* need different mode here to keep directories during iteration */+ wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);++ /* if this is a file type we don't handle, treat as ignored */+ if (wi->entry.mode == 0) {+ wi->is_ignored = 1;+ return 0;+ }++ /* if this isn't a tree, then we're done */+ if (wi->entry.mode != GIT_FILEMODE_TREE)+ return 0;++ /* detect submodules */+ error = git_submodule_lookup(NULL, wi->base.repo, wi->entry.path);+ if (error == GIT_ENOTFOUND)+ giterr_clear();++ if (error == GIT_EEXISTS) /* if contains .git, treat as untracked submod */+ error = 0;++ /* if submodule, mark as GITLINK and remove trailing slash */+ if (!error) {+ size_t len = strlen(wi->entry.path);+ assert(wi->entry.path[len - 1] == '/');+ wi->entry.path[len - 1] = '\0';+ wi->entry.mode = S_IFGITLINK;+ return 0;+ }++ if (iterator__include_trees(wi))+ return 0;++ return workdir_iterator__advance(NULL, (git_iterator *)wi);+}++int git_iterator_for_workdir(+ git_iterator **iter,+ git_repository *repo,+ git_iterator_flag_t flags,+ const char *start,+ const char *end)+{+ int error;+ workdir_iterator *wi;++ assert(iter && repo);++ if ((error = git_repository__ensure_not_bare(+ repo, "scan working directory")) < 0)+ return error;++ ITERATOR_BASE_INIT(wi, workdir, WORKDIR, repo);++ if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0)+ goto fail;++ if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||+ git_path_to_dir(&wi->path) < 0 ||+ git_ignore__for_path(repo, "", &wi->ignores) < 0)+ {+ git__free(wi);+ return -1;+ }+ wi->root_len = wi->path.size;++ if ((error = workdir_iterator__expand_dir(wi)) < 0) {+ if (error != GIT_ENOTFOUND)+ goto fail;+ giterr_clear();+ }++ *iter = (git_iterator *)wi;+ return 0;++fail:+ git_iterator_free((git_iterator *)wi);+ return error;+}+++void git_iterator_free(git_iterator *iter)+{+ if (iter == NULL)+ return;++ iter->cb->free(iter);++ git__free(iter->start);+ git__free(iter->end);++ memset(iter, 0, sizeof(*iter));++ git__free(iter);+}++int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case)+{+ bool desire_ignore_case = (ignore_case != 0);++ if (iterator__ignore_case(iter) == desire_ignore_case)+ return 0;++ if (iter->type == GIT_ITERATOR_TYPE_EMPTY) {+ if (desire_ignore_case)+ iter->flags |= GIT_ITERATOR_IGNORE_CASE;+ else+ iter->flags &= ~GIT_ITERATOR_IGNORE_CASE;+ } else {+ giterr_set(GITERR_INVALID,+ "Cannot currently set ignore case on non-empty iterators");+ return -1;+ }++ return 0;+}++git_index *git_iterator_get_index(git_iterator *iter)+{+ if (iter->type == GIT_ITERATOR_TYPE_INDEX)+ return ((index_iterator *)iter)->index;+ return NULL;+}++int git_iterator_current_tree_entry(+ const git_tree_entry **tree_entry, git_iterator *iter)+{+ if (iter->type != GIT_ITERATOR_TYPE_TREE)+ *tree_entry = NULL;+ else {+ tree_iterator_frame *tf = ((tree_iterator *)iter)->head;+ *tree_entry = (tf->current < tf->n_entries) ?+ tf->entries[tf->current]->te : NULL;+ }++ return 0;+}++int git_iterator_current_parent_tree(+ const git_tree **tree_ptr,+ git_iterator *iter,+ const char *parent_path)+{+ tree_iterator *ti = (tree_iterator *)iter;+ tree_iterator_frame *tf;+ const char *scan = parent_path;+ const git_tree_entry *te;++ *tree_ptr = NULL;++ if (iter->type != GIT_ITERATOR_TYPE_TREE)+ return 0;++ for (tf = ti->root; *scan; ) {+ if (!(tf = tf->down) ||+ tf->current >= tf->n_entries ||+ !(te = tf->entries[tf->current]->te) ||+ ti->strncomp(scan, te->filename, te->filename_len) != 0)+ return 0;++ scan += te->filename_len;+ if (*scan == '/')+ scan++;+ }++ *tree_ptr = tf->entries[tf->current]->tree;+ return 0;+}++bool git_iterator_current_is_ignored(git_iterator *iter)+{+ workdir_iterator *wi = (workdir_iterator *)iter;++ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR)+ return false;++ if (wi->is_ignored != -1)+ return (bool)(wi->is_ignored != 0);++ if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)+ wi->is_ignored = true;++ return (bool)wi->is_ignored;+}++int git_iterator_cmp(git_iterator *iter, const char *path_prefix)+{+ const git_index_entry *entry;++ /* a "done" iterator is after every prefix */+ if (git_iterator_current(&entry, iter) < 0 || entry == NULL)+ return 1;++ /* a NULL prefix is after any valid iterator */+ if (!path_prefix)+ return -1;++ return iter->prefixcomp(entry->path, path_prefix);+}++int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter)+{+ workdir_iterator *wi = (workdir_iterator *)iter;++ if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->entry.path)+ *path = NULL;+ else+ *path = &wi->path;++ return 0;+}
libgit2/src/iterator.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -9,143 +9,197 @@ #include "common.h" #include "git2/index.h"+#include "vector.h"+#include "buffer.h" typedef struct git_iterator git_iterator; typedef enum {- GIT_ITERATOR_EMPTY = 0,- GIT_ITERATOR_TREE = 1,- GIT_ITERATOR_INDEX = 2,- GIT_ITERATOR_WORKDIR = 3+ GIT_ITERATOR_TYPE_EMPTY = 0,+ GIT_ITERATOR_TYPE_TREE = 1,+ GIT_ITERATOR_TYPE_INDEX = 2,+ GIT_ITERATOR_TYPE_WORKDIR = 3, } git_iterator_type_t; +typedef enum {+ /** ignore case for entry sort order */+ GIT_ITERATOR_IGNORE_CASE = (1 << 0),+ /** force case sensitivity for entry sort order */+ GIT_ITERATOR_DONT_IGNORE_CASE = (1 << 1),+ /** return tree items in addition to blob items */+ GIT_ITERATOR_INCLUDE_TREES = (1 << 2),+ /** don't flatten trees, requiring advance_into (implies INCLUDE_TREES) */+ GIT_ITERATOR_DONT_AUTOEXPAND = (1 << 3),+} git_iterator_flag_t;++typedef struct {+ int (*current)(const git_index_entry **, git_iterator *);+ int (*advance)(const git_index_entry **, git_iterator *);+ int (*advance_into)(const git_index_entry **, git_iterator *);+ int (*seek)(git_iterator *, const char *prefix);+ int (*reset)(git_iterator *, const char *start, const char *end);+ int (*at_end)(git_iterator *);+ void (*free)(git_iterator *);+} git_iterator_callbacks;+ struct git_iterator { git_iterator_type_t type;+ git_iterator_callbacks *cb;+ git_repository *repo; char *start; char *end;- int (*current)(git_iterator *, const git_index_entry **);- int (*at_end)(git_iterator *);- int (*advance)(git_iterator *, const git_index_entry **);- int (*seek)(git_iterator *, const char *prefix);- int (*reset)(git_iterator *);- void (*free)(git_iterator *);+ int (*prefixcomp)(const char *str, const char *prefix);+ unsigned int flags; }; -extern int git_iterator_for_nothing(git_iterator **iter);--extern int git_iterator_for_tree_range(- git_iterator **iter, git_repository *repo, git_tree *tree,- const char *start, const char *end);--GIT_INLINE(int) git_iterator_for_tree(- git_iterator **iter, git_repository *repo, git_tree *tree)-{- return git_iterator_for_tree_range(iter, repo, tree, NULL, NULL);-}--extern int git_iterator_for_index_range(- git_iterator **iter, git_repository *repo,- const char *start, const char *end);+extern int git_iterator_for_nothing(+ git_iterator **out,+ git_iterator_flag_t flags,+ const char *start,+ const char *end); -GIT_INLINE(int) git_iterator_for_index(- git_iterator **iter, git_repository *repo)-{- return git_iterator_for_index_range(iter, repo, NULL, NULL);-}+/* tree iterators will match the ignore_case value from the index of the+ * repository, unless you override with a non-zero flag value+ */+extern int git_iterator_for_tree(+ git_iterator **out,+ git_tree *tree,+ git_iterator_flag_t flags,+ const char *start,+ const char *end); -extern int git_iterator_for_workdir_range(- git_iterator **iter, git_repository *repo,- const char *start, const char *end);+/* index iterators will take the ignore_case value from the index; the+ * ignore_case flags are not used+ */+extern int git_iterator_for_index(+ git_iterator **out,+ git_index *index,+ git_iterator_flag_t flags,+ const char *start,+ const char *end); -GIT_INLINE(int) git_iterator_for_workdir(- git_iterator **iter, git_repository *repo)-{- return git_iterator_for_workdir_range(iter, repo, NULL, NULL);-}+/* workdir iterators will match the ignore_case value from the index of the+ * repository, unless you override with a non-zero flag value+ */+extern int git_iterator_for_workdir(+ git_iterator **out,+ git_repository *repo,+ git_iterator_flag_t flags,+ const char *start,+ const char *end); +extern void git_iterator_free(git_iterator *iter); -/* Entry is not guaranteed to be fully populated. For a tree iterator,- * we will only populate the mode, oid and path, for example. For a workdir- * iterator, we will not populate the oid.+/* Return a git_index_entry structure for the current value the iterator+ * is looking at or NULL if the iterator is at the end. *+ * The entry may noy be fully populated. Tree iterators will only have a+ * value mode, OID, and path. Workdir iterators will not have an OID (but+ * you can use `git_iterator_current_oid()` to calculate it on demand).+ * * You do not need to free the entry. It is still "owned" by the iterator.- * Once you call `git_iterator_advance`, then content of the old entry is- * no longer guaranteed to be valid.+ * Once you call `git_iterator_advance()` then the old entry is no longer+ * guaranteed to be valid - it may be freed or just overwritten in place. */ GIT_INLINE(int) git_iterator_current(- git_iterator *iter, const git_index_entry **entry)+ const git_index_entry **entry, git_iterator *iter) {- return iter->current(iter, entry);+ return iter->cb->current(entry, iter); } -GIT_INLINE(int) git_iterator_at_end(git_iterator *iter)+/**+ * Advance to the next item for the iterator.+ *+ * If GIT_ITERATOR_INCLUDE_TREES is set, this may be a tree item. If+ * GIT_ITERATOR_DONT_AUTOEXPAND is set, calling this again when on a tree+ * item will skip over all the items under that tree.+ */+GIT_INLINE(int) git_iterator_advance(+ const git_index_entry **entry, git_iterator *iter) {- return iter->at_end(iter);+ return iter->cb->advance(entry, iter); } -GIT_INLINE(int) git_iterator_advance(- git_iterator *iter, const git_index_entry **entry)+/**+ * Iterate into a tree item (when GIT_ITERATOR_DONT_AUTOEXPAND is set).+ *+ * git_iterator_advance() steps through all items being iterated over+ * (either with or without trees, depending on GIT_ITERATOR_INCLUDE_TREES),+ * but if GIT_ITERATOR_DONT_AUTOEXPAND is set, it will skip to the next+ * sibling of a tree instead of going to the first child of the tree. In+ * that case, use this function to advance to the first child of the tree.+ *+ * If the current item is not a tree, this is a no-op.+ *+ * For working directory iterators only, a tree (i.e. directory) can be+ * empty. In that case, this function returns GIT_ENOTFOUND and does not+ * advance. That can't happen for tree and index iterators.+ */+GIT_INLINE(int) git_iterator_advance_into(+ const git_index_entry **entry, git_iterator *iter) {- return iter->advance(iter, entry);+ return iter->cb->advance_into(entry, iter); } GIT_INLINE(int) git_iterator_seek( git_iterator *iter, const char *prefix) {- return iter->seek(iter, prefix);+ return iter->cb->seek(iter, prefix); } -GIT_INLINE(int) git_iterator_reset(git_iterator *iter)+GIT_INLINE(int) git_iterator_reset(+ git_iterator *iter, const char *start, const char *end) {- return iter->reset(iter);+ return iter->cb->reset(iter, start, end); } -GIT_INLINE(void) git_iterator_free(git_iterator *iter)+GIT_INLINE(int) git_iterator_at_end(git_iterator *iter) {- if (iter == NULL)- return;-- iter->free(iter);+ return iter->cb->at_end(iter);+} - git__free(iter->start);- git__free(iter->end);+GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)+{+ return iter->type;+} - memset(iter, 0, sizeof(*iter));+GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter)+{+ return iter->repo;+} - git__free(iter);+GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter)+{+ return iter->flags; } -GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)+GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter) {- return iter->type;+ return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0); } +extern int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case);+ extern int git_iterator_current_tree_entry(- git_iterator *iter, const git_tree_entry **tree_entry);+ const git_tree_entry **entry_out, git_iterator *iter); -extern int git_iterator_current_is_ignored(git_iterator *iter);+extern int git_iterator_current_parent_tree(+ const git_tree **tree_out, git_iterator *iter, const char *parent_path); -/**- * Iterate into a workdir directory.- *- * Workdir iterators do not automatically descend into directories (so that- * when comparing two iterator entries you can detect a newly created- * directory in the workdir). As a result, you may get S_ISDIR items from- * a workdir iterator. If you wish to iterate over the contents of the- * directories you encounter, then call this function when you encounter- * a directory.- *- * If there are no files in the directory, this will end up acting like a- * regular advance and will skip past the directory, so you should be- * prepared for that case.- *- * On non-workdir iterators or if not pointing at a directory, this is a- * no-op and will not advance the iterator.- */-extern int git_iterator_advance_into_directory(- git_iterator *iter, const git_index_entry **entry);+extern bool git_iterator_current_is_ignored(git_iterator *iter); extern int git_iterator_cmp( git_iterator *iter, const char *path_prefix);++/**+ * Get full path of the current item from a workdir iterator. This will+ * return NULL for a non-workdir iterator. The git_buf is still owned by+ * the iterator; this is exposed just for efficiency.+ */+extern int git_iterator_current_workdir_path(+ git_buf **path, git_iterator *iter);++/* Return index pointer if index iterator, else NULL */+extern git_index *git_iterator_get_index(git_iterator *iter); #endif
libgit2/src/khash.h view
@@ -131,7 +131,9 @@ #endif #ifdef _MSC_VER-#define inline __inline+#define kh_inline __inline+#else+#define kh_inline inline #endif typedef khint32_t khint_t;@@ -345,7 +347,7 @@ __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) #define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \- KHASH_INIT2(name, static inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)+ KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) /* --- BEGIN OF HASH FUNCTIONS --- */ @@ -374,7 +376,7 @@ @param s Pointer to a null terminated string @return The hash value */-static inline khint_t __ac_X31_hash_string(const char *s)+static kh_inline khint_t __ac_X31_hash_string(const char *s) { khint_t h = (khint_t)*s; if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;@@ -391,7 +393,7 @@ */ #define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) -static inline khint_t __ac_Wang_hash(khint_t key)+static kh_inline khint_t __ac_Wang_hash(khint_t key) { key += ~(key << 15); key ^= (key >> 10);
libgit2/src/map.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -22,6 +22,10 @@ #define GIT_MAP_PRIVATE 2 #define GIT_MAP_TYPE 0xf #define GIT_MAP_FIXED 0x10++#ifdef __amigaos4__+#define MAP_FAILED 0+#endif typedef struct { /* memory mapped buffer */ void *data; /* data bytes */
+ libgit2/src/merge.c view
@@ -0,0 +1,296 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "repository.h"+#include "revwalk.h"+#include "buffer.h"+#include "merge.h"+#include "refs.h"+#include "git2/repository.h"+#include "git2/merge.h"+#include "git2/reset.h"+#include "commit_list.h"++int git_repository_merge_cleanup(git_repository *repo)+{+ int error = 0;+ git_buf merge_head_path = GIT_BUF_INIT,+ merge_mode_path = GIT_BUF_INIT,+ merge_msg_path = GIT_BUF_INIT;++ assert(repo);++ if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 ||+ git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 ||+ git_buf_joinpath(&merge_msg_path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)+ return -1;++ if (git_path_isfile(merge_head_path.ptr)) {+ if ((error = p_unlink(merge_head_path.ptr)) < 0)+ goto cleanup;+ }++ if (git_path_isfile(merge_mode_path.ptr))+ (void)p_unlink(merge_mode_path.ptr);++ if (git_path_isfile(merge_msg_path.ptr))+ (void)p_unlink(merge_msg_path.ptr);++cleanup:+ git_buf_free(&merge_msg_path);+ git_buf_free(&merge_mode_path);+ git_buf_free(&merge_head_path);++ return error;+}++int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_array[], size_t length)+{+ git_revwalk *walk;+ git_vector list;+ git_commit_list *result = NULL;+ int error = -1;+ unsigned int i;+ git_commit_list_node *commit;++ assert(out && repo && input_array);++ if (length < 2) {+ giterr_set(GITERR_INVALID, "At least two commits are required to find an ancestor. Provided 'length' was %u.", length);+ return -1;+ }++ if (git_vector_init(&list, length - 1, NULL) < 0)+ return -1;++ if (git_revwalk_new(&walk, repo) < 0)+ goto cleanup;++ for (i = 1; i < length; i++) {+ commit = git_revwalk__commit_lookup(walk, &input_array[i]);+ if (commit == NULL)+ goto cleanup;++ git_vector_insert(&list, commit);+ }++ commit = git_revwalk__commit_lookup(walk, &input_array[0]);+ if (commit == NULL)+ goto cleanup;++ if (git_merge__bases_many(&result, walk, commit, &list) < 0)+ goto cleanup;++ if (!result) {+ error = GIT_ENOTFOUND;+ goto cleanup;+ }++ git_oid_cpy(out, &result->item->oid);++ error = 0;++cleanup:+ git_commit_list_free(&result);+ git_revwalk_free(walk);+ git_vector_free(&list);+ return error;+}++int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two)+{+ git_revwalk *walk;+ git_vector list;+ git_commit_list *result = NULL;+ git_commit_list_node *commit;+ void *contents[1];++ if (git_revwalk_new(&walk, repo) < 0)+ return -1;++ commit = git_revwalk__commit_lookup(walk, two);+ if (commit == NULL)+ goto on_error;++ /* This is just one value, so we can do it on the stack */+ memset(&list, 0x0, sizeof(git_vector));+ contents[0] = commit;+ list.length = 1;+ list.contents = contents;++ commit = git_revwalk__commit_lookup(walk, one);+ if (commit == NULL)+ goto on_error;++ if (git_merge__bases_many(&result, walk, commit, &list) < 0)+ goto on_error;++ if (!result) {+ git_revwalk_free(walk);+ giterr_clear();+ return GIT_ENOTFOUND;+ }++ git_oid_cpy(out, &result->item->oid);+ git_commit_list_free(&result);+ git_revwalk_free(walk);++ return 0;++on_error:+ git_revwalk_free(walk);+ return -1;+}++static int interesting(git_pqueue *list)+{+ unsigned int i;+ /* element 0 isn't used - we need to start at 1 */+ for (i = 1; i < list->size; i++) {+ git_commit_list_node *commit = list->d[i];+ if ((commit->flags & STALE) == 0)+ return 1;+ }++ return 0;+}++int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos)+{+ int error;+ unsigned int i;+ git_commit_list_node *two;+ git_commit_list *result = NULL, *tmp = NULL;+ git_pqueue list;++ /* if the commit is repeated, we have a our merge base already */+ git_vector_foreach(twos, i, two) {+ if (one == two)+ return git_commit_list_insert(one, out) ? 0 : -1;+ }++ if (git_pqueue_init(&list, twos->length * 2, git_commit_list_time_cmp) < 0)+ return -1;++ if (git_commit_list_parse(walk, one) < 0)+ return -1;++ one->flags |= PARENT1;+ if (git_pqueue_insert(&list, one) < 0)+ return -1;++ git_vector_foreach(twos, i, two) {+ git_commit_list_parse(walk, two);+ two->flags |= PARENT2;+ if (git_pqueue_insert(&list, two) < 0)+ return -1;+ }++ /* as long as there are non-STALE commits */+ while (interesting(&list)) {+ git_commit_list_node *commit;+ int flags;++ commit = git_pqueue_pop(&list);++ flags = commit->flags & (PARENT1 | PARENT2 | STALE);+ if (flags == (PARENT1 | PARENT2)) {+ if (!(commit->flags & RESULT)) {+ commit->flags |= RESULT;+ if (git_commit_list_insert(commit, &result) == NULL)+ return -1;+ }+ /* we mark the parents of a merge stale */+ flags |= STALE;+ }++ for (i = 0; i < commit->out_degree; i++) {+ git_commit_list_node *p = commit->parents[i];+ if ((p->flags & flags) == flags)+ continue;++ if ((error = git_commit_list_parse(walk, p)) < 0)+ return error;++ p->flags |= flags;+ if (git_pqueue_insert(&list, p) < 0)+ return -1;+ }+ }++ git_pqueue_free(&list);++ /* filter out any stale commits in the results */+ tmp = result;+ result = NULL;++ while (tmp) {+ struct git_commit_list *next = tmp->next;+ if (!(tmp->item->flags & STALE))+ if (git_commit_list_insert_by_date(tmp->item, &result) == NULL)+ return -1;++ git__free(tmp);+ tmp = next;+ }++ *out = result;+ return 0;+}++int git_repository_mergehead_foreach(git_repository *repo,+ git_repository_mergehead_foreach_cb cb,+ void *payload)+{+ git_buf merge_head_path = GIT_BUF_INIT, merge_head_file = GIT_BUF_INIT;+ char *buffer, *line;+ size_t line_num = 1;+ git_oid oid;+ int error = 0;++ assert(repo && cb);++ if ((error = git_buf_joinpath(&merge_head_path, repo->path_repository,+ GIT_MERGE_HEAD_FILE)) < 0)+ return error;++ if ((error = git_futils_readbuffer(&merge_head_file,+ git_buf_cstr(&merge_head_path))) < 0)+ goto cleanup;++ buffer = merge_head_file.ptr;++ while ((line = git__strsep(&buffer, "\n")) != NULL) {+ if (strlen(line) != GIT_OID_HEXSZ) {+ giterr_set(GITERR_INVALID, "Unable to parse OID - invalid length");+ error = -1;+ goto cleanup;+ }++ if ((error = git_oid_fromstr(&oid, line)) < 0)+ goto cleanup;++ if (cb(&oid, payload) < 0) {+ error = GIT_EUSER;+ goto cleanup;+ }++ ++line_num;+ }++ if (*buffer) {+ giterr_set(GITERR_MERGE, "No EOL at line %d", line_num);+ error = -1;+ goto cleanup;+ }++cleanup:+ git_buf_free(&merge_head_path);+ git_buf_free(&merge_head_file);++ return error;+}
+ libgit2/src/merge.h view
@@ -0,0 +1,22 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_merge_h__+#define INCLUDE_merge_h__++#include "git2/types.h"+#include "git2/merge.h"+#include "commit_list.h"+#include "vector.h"++#define GIT_MERGE_MSG_FILE "MERGE_MSG"+#define GIT_MERGE_MODE_FILE "MERGE_MODE"++#define MERGE_CONFIG_FILE_MODE 0666++int git_merge__bases_many(git_commit_list **out, git_revwalk *walk, git_commit_list_node *one, git_vector *twos);++#endif
libgit2/src/message.c view
@@ -1,12 +1,11 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "message.h"-#include <ctype.h> static size_t line_length_without_trailing_spaces(const char *line, size_t len) {@@ -22,7 +21,7 @@ /* Greatly inspired from git.git "stripspace" */ /* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */-int git_message_prettify(git_buf *message_out, const char *message, int strip_comments)+int git_message__prettify(git_buf *message_out, const char *message, int strip_comments) { const size_t message_len = strlen(message); @@ -58,4 +57,30 @@ } return git_buf_oom(message_out) ? -1 : 0;+}++int git_message_prettify(char *message_out, size_t buffer_size, const char *message, int strip_comments)+{+ git_buf buf = GIT_BUF_INIT;+ ssize_t out_size = -1;++ if (message_out && buffer_size)+ *message_out = '\0';++ if (git_message__prettify(&buf, message, strip_comments) < 0)+ goto done;++ if (message_out && buf.size + 1 > buffer_size) { /* +1 for NUL byte */+ giterr_set(GITERR_INVALID, "Buffer too short to hold the cleaned message");+ goto done;+ }++ if (message_out)+ git_buf_copy_cstr(message_out, buffer_size, &buf);++ out_size = buf.size + 1;++done:+ git_buf_free(&buf);+ return (int)out_size; }
libgit2/src/message.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,8 +7,9 @@ #ifndef INCLUDE_message_h__ #define INCLUDE_message_h__ +#include "git2/message.h" #include "buffer.h" -int git_message_prettify(git_buf *message_out, const char *message, int strip_comments);+int git_message__prettify(git_buf *message_out, const char *message, int strip_comments); #endif /* INCLUDE_message_h__ */
libgit2/src/mwindow.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -20,26 +20,26 @@ #define DEFAULT_MAPPED_LIMIT \ ((1024 * 1024) * (sizeof(void*) >= 8 ? 8192ULL : 256UL)) -/*- * These are the global options for mmmap limits.- * TODO: allow the user to change these- */-static struct {- size_t window_size;- size_t mapped_limit;-} _mw_options = {- DEFAULT_WINDOW_SIZE,- DEFAULT_MAPPED_LIMIT,-};+size_t git_mwindow__window_size = DEFAULT_WINDOW_SIZE;+size_t git_mwindow__mapped_limit = DEFAULT_MAPPED_LIMIT; +/* Whenever you want to read or modify this, grab git__mwindow_mutex */+static git_mwindow_ctl mem_ctl;+ /* * Free all the windows in a sequence, typically because we're done * with the file */ void git_mwindow_free_all(git_mwindow_file *mwf) {- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;- unsigned int i;+ git_mwindow_ctl *ctl = &mem_ctl;+ size_t i;++ if (git_mutex_lock(&git__mwindow_mutex)) {+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");+ return;+ }+ /* * Remove these windows from the global list */@@ -67,6 +67,8 @@ mwf->windows = w->next; git__free(w); }++ git_mutex_unlock(&git__mwindow_mutex); } /*@@ -82,14 +84,13 @@ /* * Find the least-recently-used window in a file */-void git_mwindow_scan_lru(+static void git_mwindow_scan_lru( git_mwindow_file *mwf, git_mwindow **lru_w, git_mwindow **lru_l) { git_mwindow *w, *w_l; - puts("LRU"); for (w_l = NULL, w = mwf->windows; w; w = w->next) { if (!w->inuse_cnt) { /*@@ -108,12 +109,13 @@ /* * Close the least recently used window. You should check to see if- * the file descriptors need closing from time to time.+ * the file descriptors need closing from time to time. Called under+ * lock from new_window. */ static int git_mwindow_close_lru(git_mwindow_file *mwf) {- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;- unsigned int i;+ git_mwindow_ctl *ctl = &mem_ctl;+ size_t i; git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows; /* FIXME: Does this give us any advantage? */@@ -147,18 +149,20 @@ return 0; } +/* This gets called under lock from git_mwindow_open */ static git_mwindow *new_window( git_mwindow_file *mwf, git_file fd, git_off_t size, git_off_t offset) {- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;- size_t walign = _mw_options.window_size / 2;+ git_mwindow_ctl *ctl = &mem_ctl;+ size_t walign = git_mwindow__window_size / 2; git_off_t len; git_mwindow *w; w = git__malloc(sizeof(*w));+ if (w == NULL) return NULL; @@ -166,16 +170,16 @@ w->offset = (offset / walign) * walign; len = size - w->offset;- if (len > (git_off_t)_mw_options.window_size)- len = (git_off_t)_mw_options.window_size;+ if (len > (git_off_t)git_mwindow__window_size)+ len = (git_off_t)git_mwindow__window_size; ctl->mapped += (size_t)len; - while (_mw_options.mapped_limit < ctl->mapped &&+ while (git_mwindow__mapped_limit < ctl->mapped && git_mwindow_close_lru(mwf) == 0) /* nop */; /*- * We treat _mw_options.mapped_limit as a soft limit. If we can't find a+ * We treat `mapped_limit` as a soft limit. If we can't find a * window to close and are above the limit, we still mmap the new * window. */@@ -208,9 +212,14 @@ size_t extra, unsigned int *left) {- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;+ git_mwindow_ctl *ctl = &mem_ctl; git_mwindow *w = *cursor; + if (git_mutex_lock(&git__mwindow_mutex)) {+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");+ return NULL;+ }+ if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) { if (w) { w->inuse_cnt--;@@ -228,8 +237,10 @@ */ if (!w) { w = new_window(mwf, mwf->fd, mwf->size, offset);- if (w == NULL)+ if (w == NULL) {+ git_mutex_unlock(&git__mwindow_mutex); return NULL;+ } w->next = mwf->windows; mwf->windows = w; }@@ -247,26 +258,62 @@ if (left) *left = (unsigned int)(w->window_map.len - offset); - fflush(stdout);+ git_mutex_unlock(&git__mwindow_mutex); return (unsigned char *) w->window_map.data + offset; } int git_mwindow_file_register(git_mwindow_file *mwf) {- git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl;+ git_mwindow_ctl *ctl = &mem_ctl;+ int ret; + if (git_mutex_lock(&git__mwindow_mutex)) {+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");+ return -1;+ }+ if (ctl->windowfiles.length == 0 &&- git_vector_init(&ctl->windowfiles, 8, NULL) < 0)+ git_vector_init(&ctl->windowfiles, 8, NULL) < 0) {+ git_mutex_unlock(&git__mwindow_mutex); return -1;+ } - return git_vector_insert(&ctl->windowfiles, mwf);+ ret = git_vector_insert(&ctl->windowfiles, mwf);+ git_mutex_unlock(&git__mwindow_mutex);++ return ret; } +void git_mwindow_file_deregister(git_mwindow_file *mwf)+{+ git_mwindow_ctl *ctl = &mem_ctl;+ git_mwindow_file *cur;+ size_t i;++ if (git_mutex_lock(&git__mwindow_mutex))+ return;++ git_vector_foreach(&ctl->windowfiles, i, cur) {+ if (cur == mwf) {+ git_vector_remove(&ctl->windowfiles, i);+ git_mutex_unlock(&git__mwindow_mutex);+ return;+ }+ }+ git_mutex_unlock(&git__mwindow_mutex);+}+ void git_mwindow_close(git_mwindow **window) { git_mwindow *w = *window; if (w) {+ if (git_mutex_lock(&git__mwindow_mutex)) {+ giterr_set(GITERR_THREAD, "unable to lock mwindow mutex");+ return;+ }+ w->inuse_cnt--;+ git_mutex_unlock(&git__mwindow_mutex); *window = NULL; } }
libgit2/src/mwindow.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -38,8 +38,8 @@ int git_mwindow_contains(git_mwindow *win, git_off_t offset); void git_mwindow_free_all(git_mwindow_file *mwf); unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left);-void git_mwindow_scan_lru(git_mwindow_file *mwf, git_mwindow **lru_w, git_mwindow **lru_l); int git_mwindow_file_register(git_mwindow_file *mwf);+void git_mwindow_file_deregister(git_mwindow_file *mwf); void git_mwindow_close(git_mwindow **w_cursor); #endif
libgit2/src/netops.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,15 +10,26 @@ # include <sys/select.h> # include <sys/time.h> # include <netdb.h>+# include <netinet/in.h>+# include <arpa/inet.h> #else-# include <winsock2.h>-# include <Ws2tcpip.h>+# include <ws2tcpip.h> # ifdef _MSC_VER-# pragma comment(lib, "Ws2_32.lib")+# pragma comment(lib, "ws2_32") # endif #endif +#ifdef __FreeBSD__+# include <netinet/in.h>+#endif +#ifdef GIT_SSL+# include <openssl/ssl.h>+# include <openssl/err.h>+# include <openssl/x509v3.h>+#endif++#include <ctype.h> #include "git2/errors.h" #include "common.h"@@ -29,14 +40,15 @@ #ifdef GIT_WIN32 static void net_set_error(const char *str) {- int size, error = WSAGetLastError();- LPSTR err_str = NULL;-- size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,- 0, error, 0, (LPSTR)&err_str, 0, 0);+ int error = WSAGetLastError();+ char * win32_error = git_win32_get_error_message(error); - giterr_set(GITERR_NET, "%s: %s", str, err_str);- LocalFree(err_str);+ if (win32_error) {+ giterr_set(GITERR_NET, "%s: %s", str, win32_error);+ git__free(win32_error);+ } else {+ giterr_set(GITERR_NET, str);+ } } #else static void net_set_error(const char *str)@@ -45,21 +57,66 @@ } #endif -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd)+#ifdef GIT_SSL+static int ssl_set_error(gitno_ssl *ssl, int error) {- memset(buf, 0x0, sizeof(gitno_buffer));- memset(data, 0x0, len);- buf->data = data;- buf->len = len;- buf->offset = 0;- buf->fd = fd;+ int err;+ unsigned long e;++ err = SSL_get_error(ssl->ssl, error);++ assert(err != SSL_ERROR_WANT_READ);+ assert(err != SSL_ERROR_WANT_WRITE);++ switch (err) {+ case SSL_ERROR_WANT_CONNECT:+ case SSL_ERROR_WANT_ACCEPT:+ giterr_set(GITERR_NET, "SSL error: connection failure\n");+ break;+ case SSL_ERROR_WANT_X509_LOOKUP:+ giterr_set(GITERR_NET, "SSL error: x509 error\n");+ break;+ case SSL_ERROR_SYSCALL:+ e = ERR_get_error();+ if (e > 0) {+ giterr_set(GITERR_NET, "SSL error: %s",+ ERR_error_string(e, NULL));+ break;+ } else if (error < 0) {+ giterr_set(GITERR_OS, "SSL error: syscall failure");+ break;+ }+ giterr_set(GITERR_NET, "SSL error: received early EOF");+ break;+ case SSL_ERROR_SSL:+ e = ERR_get_error();+ giterr_set(GITERR_NET, "SSL error: %s",+ ERR_error_string(e, NULL));+ break;+ case SSL_ERROR_NONE:+ case SSL_ERROR_ZERO_RETURN:+ default:+ giterr_set(GITERR_NET, "SSL error: unknown error");+ break;+ }+ return -1; }+#endif int gitno_recv(gitno_buffer *buf) {+ return buf->recv(buf);+}++#ifdef GIT_SSL+static int gitno__recv_ssl(gitno_buffer *buf)+{ int ret; - ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0);+ do {+ ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset);+ } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ);+ if (ret < 0) { net_set_error("Error receiving socket data"); return -1;@@ -68,7 +125,50 @@ buf->offset += ret; return ret; }+#endif +static int gitno__recv(gitno_buffer *buf)+{+ int ret;++ ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0);+ if (ret < 0) {+ net_set_error("Error receiving socket data");+ return -1;+ }++ buf->offset += ret;+ return ret;+}++void gitno_buffer_setup_callback(+ gitno_socket *socket,+ gitno_buffer *buf,+ char *data,+ size_t len,+ int (*recv)(gitno_buffer *buf), void *cb_data)+{+ memset(data, 0x0, len);+ buf->data = data;+ buf->len = len;+ buf->offset = 0;+ buf->socket = socket;+ buf->recv = recv;+ buf->cb_data = cb_data;+}++void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)+{+#ifdef GIT_SSL+ if (socket->ssl.ctx) {+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);+ return;+ }+#endif++ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);+}+ /* Consume up to ptr and move the rest of the buffer to the beginning */ void gitno_consume(gitno_buffer *buf, const char *ptr) {@@ -92,24 +192,284 @@ buf->offset -= cons; } -int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port)+#ifdef GIT_SSL++static int gitno_ssl_teardown(gitno_ssl *ssl) {+ int ret;++ ret = SSL_shutdown(ssl->ssl);+ if (ret < 0)+ ret = ssl_set_error(ssl, ret);+ else+ ret = 0;++ SSL_free(ssl->ssl);+ SSL_CTX_free(ssl->ctx);+ return ret;+}++/* Match host names according to RFC 2818 rules */+static int match_host(const char *pattern, const char *host)+{+ for (;;) {+ char c = tolower(*pattern++);++ if (c == '\0')+ return *host ? -1 : 0;++ if (c == '*') {+ c = *pattern;+ /* '*' at the end matches everything left */+ if (c == '\0')+ return 0;++ /*+ * We've found a pattern, so move towards the next matching+ * char. The '.' is handled specially because wildcards aren't+ * allowed to cross subdomains.+ */++ while(*host) {+ char h = tolower(*host);+ if (c == h)+ return match_host(pattern, host++);+ if (h == '.')+ return match_host(pattern, host);+ host++;+ }+ return -1;+ }++ if (c != tolower(*host++))+ return -1;+ }++ return -1;+}++static int check_host_name(const char *name, const char *host)+{+ if (!strcasecmp(name, host))+ return 0;++ if (match_host(name, host) < 0)+ return -1;++ return 0;+}++static int verify_server_cert(gitno_ssl *ssl, const char *host)+{+ X509 *cert;+ X509_NAME *peer_name;+ ASN1_STRING *str;+ unsigned char *peer_cn = NULL;+ int matched = -1, type = GEN_DNS;+ GENERAL_NAMES *alts;+ struct in6_addr addr6;+ struct in_addr addr4;+ void *addr;+ int i = -1,j;++ if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {+ giterr_set(GITERR_SSL, "The SSL certificate is invalid");+ return -1;+ }++ /* Try to parse the host as an IP address to see if it is */+ if (p_inet_pton(AF_INET, host, &addr4)) {+ type = GEN_IPADD;+ addr = &addr4;+ } else {+ if(p_inet_pton(AF_INET6, host, &addr6)) {+ type = GEN_IPADD;+ addr = &addr6;+ }+ }+++ cert = SSL_get_peer_certificate(ssl->ssl);++ /* Check the alternative names */+ alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);+ if (alts) {+ int num;++ num = sk_GENERAL_NAME_num(alts);+ for (i = 0; i < num && matched != 1; i++) {+ const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);+ const char *name = (char *) ASN1_STRING_data(gn->d.ia5);+ size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);++ /* Skip any names of a type we're not looking for */+ if (gn->type != type)+ continue;++ if (type == GEN_DNS) {+ /* If it contains embedded NULs, don't even try */+ if (memchr(name, '\0', namelen))+ continue;++ if (check_host_name(name, host) < 0)+ matched = 0;+ else+ matched = 1;+ } else if (type == GEN_IPADD) {+ /* Here name isn't so much a name but a binary representation of the IP */+ matched = !!memcmp(name, addr, namelen);+ }+ }+ }+ GENERAL_NAMES_free(alts);++ if (matched == 0)+ goto cert_fail;++ if (matched == 1)+ return 0;++ /* If no alternative names are available, check the common name */+ peer_name = X509_get_subject_name(cert);+ if (peer_name == NULL)+ goto on_error;++ if (peer_name) {+ /* Get the index of the last CN entry */+ while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)+ i = j;+ }++ if (i < 0)+ goto on_error;++ str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));+ if (str == NULL)+ goto on_error;++ /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */+ if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {+ int size = ASN1_STRING_length(str);++ if (size > 0) {+ peer_cn = OPENSSL_malloc(size + 1);+ GITERR_CHECK_ALLOC(peer_cn);+ memcpy(peer_cn, ASN1_STRING_data(str), size);+ peer_cn[size] = '\0';+ }+ } else {+ int size = ASN1_STRING_to_UTF8(&peer_cn, str);+ GITERR_CHECK_ALLOC(peer_cn);+ if (memchr(peer_cn, '\0', size))+ goto cert_fail;+ }++ if (check_host_name((char *)peer_cn, host) < 0)+ goto cert_fail;++ OPENSSL_free(peer_cn);++ return 0;++on_error:+ OPENSSL_free(peer_cn);+ return ssl_set_error(ssl, 0);++cert_fail:+ OPENSSL_free(peer_cn);+ giterr_set(GITERR_SSL, "Certificate host name check failed");+ return -1;+}++static int ssl_setup(gitno_socket *socket, const char *host, int flags)+{+ int ret;++ SSL_library_init();+ SSL_load_error_strings();+ socket->ssl.ctx = SSL_CTX_new(SSLv23_method());+ if (socket->ssl.ctx == NULL)+ return ssl_set_error(&socket->ssl, 0);++ SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY);+ SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL);+ if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx))+ return ssl_set_error(&socket->ssl, 0);++ socket->ssl.ssl = SSL_new(socket->ssl.ctx);+ if (socket->ssl.ssl == NULL)+ return ssl_set_error(&socket->ssl, 0);++ if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)+ return ssl_set_error(&socket->ssl, ret);++ if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)+ return ssl_set_error(&socket->ssl, ret);++ if (GITNO_CONNECT_SSL_NO_CHECK_CERT & flags)+ return 0;++ return verify_server_cert(&socket->ssl, host);+}+#endif++static int gitno__close(GIT_SOCKET s)+{+#ifdef GIT_WIN32+ if (SOCKET_ERROR == closesocket(s))+ return -1;++ if (0 != WSACleanup()) {+ giterr_set(GITERR_OS, "Winsock cleanup failed");+ return -1;+ }++ return 0;+#else+ return close(s);+#endif+}++int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags)+{ struct addrinfo *info = NULL, *p; struct addrinfo hints;- int ret; GIT_SOCKET s = INVALID_SOCKET;+ int ret; +#ifdef GIT_WIN32+ /* on win32, the WSA context needs to be initialized+ * before any socket calls can be performed */+ WSADATA wsd;++ if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {+ giterr_set(GITERR_OS, "Winsock init failed");+ return -1;+ }++ if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {+ WSACleanup();+ giterr_set(GITERR_OS, "Winsock init failed");+ return -1;+ }+#endif++ /* Zero the socket structure provided */+ memset(s_out, 0x0, sizeof(gitno_socket));+ memset(&hints, 0x0, sizeof(struct addrinfo));- hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM;+ hints.ai_family = AF_UNSPEC; - if ((ret = getaddrinfo(host, port, &hints, &info)) < 0) {- giterr_set(GITERR_NET, "Failed to resolve address for %s: %s", host, gai_strerror(ret));+ if ((ret = p_getaddrinfo(host, port, &hints, &info)) < 0) {+ giterr_set(GITERR_NET,+ "Failed to resolve address for %s: %s", host, p_gai_strerror(ret)); return -1; } for (p = info; p != NULL; p = p->ai_next) { s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);+ if (s == INVALID_SOCKET) { net_set_error("error creating socket"); break;@@ -119,30 +479,67 @@ break; /* If we can't connect, try the next one */- gitno_close(s);+ gitno__close(s); s = INVALID_SOCKET; } /* Oops, we couldn't connect to any address */ if (s == INVALID_SOCKET && p == NULL) { giterr_set(GITERR_OS, "Failed to connect to %s", host);+ p_freeaddrinfo(info); return -1; } - freeaddrinfo(info);- *sock = s;+ s_out->socket = s;+ p_freeaddrinfo(info);++#ifdef GIT_SSL+ if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)+ return -1;+#else+ /* SSL is not supported */+ if (flags & GITNO_CONNECT_SSL) {+ giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2.");+ return -1;+ }+#endif+ return 0; } -int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags)+#ifdef GIT_SSL+static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags) { int ret; size_t off = 0; + GIT_UNUSED(flags);+ while (off < len) {- errno = 0;+ ret = SSL_write(ssl->ssl, msg + off, len - off);+ if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE)+ return ssl_set_error(ssl, ret); - ret = p_send(s, msg + off, len - off, flags);+ off += ret;+ } ++ return off;+}+#endif++int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)+{+ int ret;+ size_t off = 0;++#ifdef GIT_SSL+ if (socket->ssl.ctx)+ return gitno_send_ssl(&socket->ssl, msg, len, flags);+#endif++ while (off < len) {+ errno = 0;+ ret = p_send(socket->socket, msg + off, len - off, flags); if (ret < 0) { net_set_error("Error sending data"); return -1;@@ -154,19 +551,17 @@ return (int)off; } --#ifdef GIT_WIN32-int gitno_close(GIT_SOCKET s)-{- return closesocket(s) == SOCKET_ERROR ? -1 : 0;-}-#else-int gitno_close(GIT_SOCKET s)+int gitno_close(gitno_socket *s) {- return close(s);-}+#ifdef GIT_SSL+ if (s->ssl.ctx &&+ gitno_ssl_teardown(&s->ssl) < 0)+ return -1; #endif + return gitno__close(s->socket);+}+ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) { fd_set fds;@@ -176,33 +571,60 @@ tv.tv_usec = usec; FD_ZERO(&fds);- FD_SET(buf->fd, &fds);+ FD_SET(buf->socket->socket, &fds); /* The select(2) interface is silly */- return select((int)buf->fd + 1, &fds, NULL, NULL, &tv);+ return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv); } -int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)+int gitno_extract_url_parts(+ char **host,+ char **port,+ char **username,+ char **password,+ const char *url,+ const char *default_port) {- char *colon, *slash, *delim;+ char *colon, *slash, *at, *end;+ const char *start; + /*+ *+ * ==> [user[:pass]@]hostname.tld[:port]/resource+ */+ colon = strchr(url, ':'); slash = strchr(url, '/');+ at = strchr(url, '@'); if (slash == NULL) { giterr_set(GITERR_NET, "Malformed URL: missing /"); return -1; } + start = url;+ if (at && at < slash) {+ start = at+1;+ *username = git__substrdup(url, at - url);+ }++ if (colon && colon < at) {+ git__free(*username);+ *username = git__substrdup(url, colon-url);+ *password = git__substrdup(colon+1, at-colon-1);+ colon = strchr(at, ':');+ }+ if (colon == NULL) { *port = git__strdup(default_port); } else {- *port = git__strndup(colon + 1, slash - colon - 1);+ *port = git__substrdup(colon + 1, slash - colon - 1); } GITERR_CHECK_ALLOC(*port); - delim = colon == NULL ? slash : colon;- *host = git__strndup(url, delim - url);+ end = colon == NULL ? slash : colon;++ *host = git__substrdup(start, end - start); GITERR_CHECK_ALLOC(*host); return 0;
libgit2/src/netops.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,25 +8,70 @@ #define INCLUDE_netops_h__ #include "posix.h"+#include "common.h" -typedef struct gitno_buffer {+#ifdef GIT_SSL+# include <openssl/ssl.h>+#endif++struct gitno_ssl {+#ifdef GIT_SSL+ SSL_CTX *ctx;+ SSL *ssl;+#else+ size_t dummy;+#endif+};++typedef struct gitno_ssl gitno_ssl;++/* Represents a socket that may or may not be using SSL */+struct gitno_socket {+ GIT_SOCKET socket;+ gitno_ssl ssl;+};++typedef struct gitno_socket gitno_socket;++struct gitno_buffer { char *data; size_t len; size_t offset;- GIT_SOCKET fd;-} gitno_buffer;+ gitno_socket *socket;+ int (*recv)(struct gitno_buffer *buffer);+ void *cb_data;+}; -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd);+typedef struct gitno_buffer gitno_buffer;++/* Flags to gitno_connect */+enum {+ /* Attempt to create an SSL connection. */+ GITNO_CONNECT_SSL = 1,++ /* Valid only when GITNO_CONNECT_SSL is also specified.+ * Indicates that the server certificate should not be validated. */+ GITNO_CONNECT_SSL_NO_CHECK_CERT = 2,+};++void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len);+void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); int gitno_recv(gitno_buffer *buf);+ void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -int gitno_connect(GIT_SOCKET *s, const char *host, const char *port);-int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags);-int gitno_close(GIT_SOCKET s);-int gitno_send_chunk_size(int s, size_t len);+int gitno_connect(gitno_socket *socket, const char *host, const char *port, int flags);+int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags);+int gitno_close(gitno_socket *s); int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); -int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port);+int gitno_extract_url_parts(+ char **host,+ char **port,+ char **username,+ char **password,+ const char *url,+ const char *default_port); #endif
libgit2/src/notes.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -11,56 +11,68 @@ #include "refs.h" #include "config.h" #include "iterator.h"+#include "signature.h" -static int find_subtree(git_tree **subtree, const git_oid *root,- git_repository *repo, const char *target, int *fanout)+static int find_subtree_in_current_level(+ git_tree **out,+ git_repository *repo,+ git_tree *parent,+ const char *annotated_object_sha,+ int fanout) {- int error;- unsigned int i;- git_tree *tree;+ size_t i; const git_tree_entry *entry; - *subtree = NULL;+ *out = NULL; - error = git_tree_lookup(&tree, repo, root);- if (error < 0)- return error;+ if (parent == NULL)+ return GIT_ENOTFOUND; - for (i=0; i<git_tree_entrycount(tree); i++) {- entry = git_tree_entry_byindex(tree, i);+ for (i = 0; i < git_tree_entrycount(parent); i++) {+ entry = git_tree_entry_byindex(parent, i); if (!git__ishex(git_tree_entry_name(entry))) continue; - /*- * A notes tree follows a strict byte-based progressive fanout- * (i.e. using 2/38, 2/2/36, etc. fanouts, not e.g. 4/36 fanout)- */-- if (S_ISDIR(git_tree_entry_attributes(entry))+ if (S_ISDIR(git_tree_entry_filemode(entry)) && strlen(git_tree_entry_name(entry)) == 2- && !strncmp(git_tree_entry_name(entry), target + *fanout, 2)) {+ && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2))+ return git_tree_lookup(out, repo, git_tree_entry_id(entry)); - /* found matching subtree - unpack and resume lookup */+ /* Not a DIR, so do we have an already existing blob? */+ if (!strcmp(git_tree_entry_name(entry), annotated_object_sha + fanout))+ return GIT_EEXISTS;+ } - git_oid subtree_sha;- git_oid_cpy(&subtree_sha, git_tree_entry_id(entry));- git_tree_free(tree);+ return GIT_ENOTFOUND;+} - *fanout += 2;+static int find_subtree_r(git_tree **out, git_tree *root,+ git_repository *repo, const char *target, int *fanout)+{+ int error;+ git_tree *subtree = NULL; - return find_subtree(subtree, &subtree_sha, repo,- target, fanout);- }+ *out = NULL;++ error = find_subtree_in_current_level(&subtree, repo, root, target, *fanout);+ if (error == GIT_EEXISTS) {+ return git_tree_lookup(out, repo, git_tree_id(root)); } - *subtree = tree;- return 0;+ if (error < 0)+ return error;++ *fanout += 2;+ error = find_subtree_r(out, subtree, repo, target, fanout);+ git_tree_free(subtree);++ return error; } static int find_blob(git_oid *blob, git_tree *tree, const char *target) {- unsigned int i;+ size_t i; const git_tree_entry *entry; for (i=0; i<git_tree_entrycount(tree); i++) {@@ -76,191 +88,285 @@ return GIT_ENOTFOUND; } -static int note_write(git_oid *out, git_repository *repo,- git_signature *author, git_signature *committer,- const char *notes_ref, const char *note,- const git_oid *tree_sha, const char *target,- int nparents, git_commit **parents)+static int tree_write(+ git_tree **out,+ git_repository *repo,+ git_tree *source_tree,+ const git_oid *object_oid,+ const char *treeentry_name,+ unsigned int attributes) {- int error, fanout = 0;- git_oid oid;- git_tree *tree = NULL;- git_tree_entry *entry;- git_treebuilder *tb;-- /* check for existing notes tree */+ int error;+ git_treebuilder *tb = NULL;+ const git_tree_entry *entry;+ git_oid tree_oid; - if (tree_sha) {- error = find_subtree(&tree, tree_sha, repo, target, &fanout);- if (error < 0)- return error;+ if ((error = git_treebuilder_create(&tb, source_tree)) < 0)+ goto cleanup; - error = find_blob(&oid, tree, target + fanout);- if (error != GIT_ENOTFOUND) {- git_tree_free(tree);- if (!error) {- giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", target);- error = GIT_EEXISTS;- }- return error;- }+ if (object_oid) {+ if ((error = git_treebuilder_insert(+ &entry, tb, treeentry_name, object_oid, attributes)) < 0)+ goto cleanup;+ } else {+ if ((error = git_treebuilder_remove(tb, treeentry_name)) < 0)+ goto cleanup; } - /* no matching tree entry - add note object to target tree */+ if ((error = git_treebuilder_write(&tree_oid, repo, tb)) < 0)+ goto cleanup; - error = git_treebuilder_create(&tb, tree);- git_tree_free(tree);+ error = git_tree_lookup(out, repo, &tree_oid); - if (error < 0)- return error;+cleanup:+ git_treebuilder_free(tb);+ return error;+} - if (!tree_sha)- /* no notes tree yet - create fanout */- fanout += 2;+static int manipulate_note_in_tree_r(+ git_tree **out,+ git_repository *repo,+ git_tree *parent,+ git_oid *note_oid,+ const char *annotated_object_sha,+ int fanout,+ int (*note_exists_cb)(+ git_tree **out,+ git_repository *repo,+ git_tree *parent,+ git_oid *note_oid,+ const char *annotated_object_sha,+ int fanout,+ int current_error),+ int (*note_notfound_cb)(+ git_tree **out,+ git_repository *repo,+ git_tree *parent,+ git_oid *note_oid,+ const char *annotated_object_sha,+ int fanout,+ int current_error))+{+ int error;+ git_tree *subtree = NULL, *new = NULL;+ char subtree_name[3]; - /* create note object */- error = git_blob_create_frombuffer(&oid, repo, note, strlen(note));- if (error < 0) {- git_treebuilder_free(tb);- return error;- }+ error = find_subtree_in_current_level(+ &subtree, repo, parent, annotated_object_sha, fanout); - error = git_treebuilder_insert(&entry, tb, target + fanout, &oid, 0100644);- if (error < 0) {- /* libgit2 doesn't support object removal (gc) yet */- /* we leave an orphaned blob object behind - TODO */+ if (error == GIT_EEXISTS) {+ error = note_exists_cb(+ out, repo, parent, note_oid, annotated_object_sha, fanout, error);+ goto cleanup;+ } - git_treebuilder_free(tb);- return error;+ if (error == GIT_ENOTFOUND) {+ error = note_notfound_cb(+ out, repo, parent, note_oid, annotated_object_sha, fanout, error);+ goto cleanup; } - if (out)- git_oid_cpy(out, git_tree_entry_id(entry));+ if (error < 0)+ goto cleanup; - error = git_treebuilder_write(&oid, repo, tb);- git_treebuilder_free(tb);+ /* An existing fanout has been found, let's dig deeper */+ error = manipulate_note_in_tree_r(+ &new, repo, subtree, note_oid, annotated_object_sha,+ fanout + 2, note_exists_cb, note_notfound_cb); if (error < 0)- return 0;+ goto cleanup; - if (!tree_sha) {- /* create fanout subtree */+ strncpy(subtree_name, annotated_object_sha + fanout, 2);+ subtree_name[2] = '\0'; - char subtree[3];- strncpy(subtree, target, 2);- subtree[2] = '\0';+ error = tree_write(out, repo, parent, git_tree_id(new),+ subtree_name, GIT_FILEMODE_TREE); - error = git_treebuilder_create(&tb, NULL);- if (error < 0)- return error; - error = git_treebuilder_insert(NULL, tb, subtree, &oid, 0040000);- if (error < 0) {- git_treebuilder_free(tb);- return error;- }+cleanup:+ git_tree_free(new);+ git_tree_free(subtree);+ return error;+} - error = git_treebuilder_write(&oid, repo, tb);+static int remove_note_in_tree_eexists_cb(+ git_tree **out,+ git_repository *repo,+ git_tree *parent,+ git_oid *note_oid,+ const char *annotated_object_sha,+ int fanout,+ int current_error)+{+ GIT_UNUSED(note_oid);+ GIT_UNUSED(current_error); - git_treebuilder_free(tb);+ return tree_write(out, repo, parent, NULL, annotated_object_sha + fanout, 0);+} - if (error < 0)- return error;- }+static int remove_note_in_tree_enotfound_cb(+ git_tree **out,+ git_repository *repo,+ git_tree *parent,+ git_oid *note_oid,+ const char *annotated_object_sha,+ int fanout,+ int current_error)+{+ GIT_UNUSED(out);+ GIT_UNUSED(repo);+ GIT_UNUSED(parent);+ GIT_UNUSED(note_oid);+ GIT_UNUSED(fanout); - /* create new notes commit */+ giterr_set(GITERR_REPOSITORY, "Object '%s' has no note", annotated_object_sha);+ return current_error;+} - error = git_tree_lookup(&tree, repo, &oid);- if (error < 0)- return error;+static int insert_note_in_tree_eexists_cb(git_tree **out,+ git_repository *repo,+ git_tree *parent,+ git_oid *note_oid,+ const char *annotated_object_sha,+ int fanout,+ int current_error)+{+ GIT_UNUSED(out);+ GIT_UNUSED(repo);+ GIT_UNUSED(parent);+ GIT_UNUSED(note_oid);+ GIT_UNUSED(fanout); - error = git_commit_create(&oid, repo, notes_ref, author, committer,- NULL, GIT_NOTES_DEFAULT_MSG_ADD,- tree, nparents, (const git_commit **) parents);+ giterr_set(GITERR_REPOSITORY, "Note for '%s' exists already", annotated_object_sha);+ return current_error;+} - git_tree_free(tree);+static int insert_note_in_tree_enotfound_cb(git_tree **out,+ git_repository *repo,+ git_tree *parent,+ git_oid *note_oid,+ const char *annotated_object_sha,+ int fanout,+ int current_error)+{+ GIT_UNUSED(current_error); - return error;+ /* No existing fanout at this level, insert in place */+ return tree_write(+ out,+ repo,+ parent,+ note_oid,+ annotated_object_sha + fanout,+ GIT_FILEMODE_BLOB); } -static int note_lookup(git_note **out, git_repository *repo,- const git_oid *tree_sha, const char *target)+static int note_write(git_oid *out,+ git_repository *repo,+ const git_signature *author,+ const git_signature *committer,+ const char *notes_ref,+ const char *note,+ git_tree *commit_tree,+ const char *target,+ git_commit **parents,+ int allow_note_overwrite) {- int error, fanout = 0;+ int error; git_oid oid;- git_blob *blob;- git_tree *tree;- git_note *note;+ git_tree *tree = NULL; - error = find_subtree(&tree, tree_sha, repo, target, &fanout);- if (error < 0)- return error;+ // TODO: should we apply filters?+ /* create note object */+ if ((error = git_blob_create_frombuffer(&oid, repo, note, strlen(note))) < 0)+ goto cleanup; - error = find_blob(&oid, tree, target + fanout);+ if ((error = manipulate_note_in_tree_r(+ &tree, repo, commit_tree, &oid, target, 0,+ allow_note_overwrite ? insert_note_in_tree_enotfound_cb : insert_note_in_tree_eexists_cb,+ insert_note_in_tree_enotfound_cb)) < 0)+ goto cleanup; + if (out)+ git_oid_cpy(out, &oid);++ error = git_commit_create(&oid, repo, notes_ref, author, committer,+ NULL, GIT_NOTES_DEFAULT_MSG_ADD,+ tree, *parents == NULL ? 0 : 1, (const git_commit **) parents);++cleanup: git_tree_free(tree);- if (error < 0)- return error;+ return error;+} - error = git_blob_lookup(&blob, repo, &oid);- if (error < 0)- return error;+static int note_new(git_note **out, git_oid *note_oid, git_blob *blob)+{+ git_note *note = NULL; - note = git__malloc(sizeof(git_note));+ note = (git_note *)git__malloc(sizeof(git_note)); GITERR_CHECK_ALLOC(note); - git_oid_cpy(¬e->oid, &oid);- note->message = git__strdup(git_blob_rawcontent(blob));+ git_oid_cpy(¬e->oid, note_oid);+ note->message = git__strdup((char *)git_blob_rawcontent(blob)); GITERR_CHECK_ALLOC(note->message); *out = note; - git_blob_free(blob);- return error;+ return 0; } -static int note_remove(git_repository *repo,- git_signature *author, git_signature *committer,- const char *notes_ref, const git_oid *tree_sha,- const char *target, int nparents, git_commit **parents)+static int note_lookup(git_note **out, git_repository *repo,+ git_tree *tree, const char *target) { int error, fanout = 0; git_oid oid;- git_tree *tree;- git_treebuilder *tb;+ git_blob *blob = NULL;+ git_note *note = NULL;+ git_tree *subtree = NULL; - error = find_subtree(&tree, tree_sha, repo, target, &fanout);- if (error < 0)- return error;+ if ((error = find_subtree_r(&subtree, tree, repo, target, &fanout)) < 0)+ goto cleanup; - error = find_blob(&oid, tree, target + fanout);- if (!error)- error = git_treebuilder_create(&tb, tree);+ if ((error = find_blob(&oid, subtree, target + fanout)) < 0)+ goto cleanup; - git_tree_free(tree);- if (error < 0)- return error;+ if ((error = git_blob_lookup(&blob, repo, &oid)) < 0)+ goto cleanup; - error = git_treebuilder_remove(tb, target + fanout);- if (!error)- error = git_treebuilder_write(&oid, repo, tb);+ if ((error = note_new(¬e, &oid, blob)) < 0)+ goto cleanup; - git_treebuilder_free(tb);- if (error < 0)- return error;+ *out = note; - /* create new notes commit */+cleanup:+ git_tree_free(subtree);+ git_blob_free(blob);+ return error;+} - error = git_tree_lookup(&tree, repo, &oid);- if (error < 0)- return error;+static int note_remove(git_repository *repo,+ const git_signature *author, const git_signature *committer,+ const char *notes_ref, git_tree *tree,+ const char *target, git_commit **parents)+{+ int error;+ git_tree *tree_after_removal = NULL;+ git_oid oid; - error = git_commit_create(&oid, repo, notes_ref, author, committer,- NULL, GIT_NOTES_DEFAULT_MSG_RM,- tree, nparents, (const git_commit **) parents);+ if ((error = manipulate_note_in_tree_r(+ &tree_after_removal, repo, tree, NULL, target, 0,+ remove_note_in_tree_eexists_cb, remove_note_in_tree_enotfound_cb)) < 0)+ goto cleanup; - git_tree_free(tree);+ error = git_commit_create(&oid, repo, notes_ref, author, committer,+ NULL, GIT_NOTES_DEFAULT_MSG_RM,+ tree_after_removal,+ *parents == NULL ? 0 : 1,+ (const git_commit **) parents); +cleanup:+ git_tree_free(tree_after_removal); return error; } @@ -291,134 +397,108 @@ return note_get_default_ref(notes_ref, repo); } -static int retrieve_note_tree_oid(git_oid *tree_oid_out, git_repository *repo, const char *notes_ref)+static int retrieve_note_tree_and_commit(+ git_tree **tree_out,+ git_commit **commit_out,+ git_repository *repo,+ const char **notes_ref) {- int error = -1;- git_commit *commit = NULL;+ int error; git_oid oid; - if ((error = git_reference_name_to_oid(&oid, repo, notes_ref)) < 0)- goto cleanup;+ if ((error = normalize_namespace(notes_ref, repo)) < 0)+ return error; - if (git_commit_lookup(&commit, repo, &oid) < 0)- goto cleanup;+ if ((error = git_reference_name_to_id(&oid, repo, *notes_ref)) < 0)+ return error; - git_oid_cpy(tree_oid_out, git_commit_tree_oid(commit));+ if (git_commit_lookup(commit_out, repo, &oid) < 0)+ return error; - error = 0;+ if ((error = git_commit_tree(tree_out, *commit_out)) < 0)+ return error; -cleanup:- git_commit_free(commit);- return error;+ return 0; } int git_note_read(git_note **out, git_repository *repo, const char *notes_ref, const git_oid *oid) { int error;- char *target;- git_oid sha;-- *out = NULL;-- if (normalize_namespace(¬es_ref, repo) < 0)- return -1;-- if ((error = retrieve_note_tree_oid(&sha, repo, notes_ref)) < 0)- return error;+ char *target = NULL;+ git_tree *tree = NULL;+ git_commit *commit = NULL; target = git_oid_allocfmt(oid); GITERR_CHECK_ALLOC(target); - error = note_lookup(out, repo, &sha, target);+ if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref)) < 0)+ goto cleanup; + error = note_lookup(out, repo, tree, target);++cleanup: git__free(target);+ git_tree_free(tree);+ git_commit_free(commit); return error; } int git_note_create(- git_oid *out, git_repository *repo,- git_signature *author, git_signature *committer,- const char *notes_ref, const git_oid *oid,- const char *note)+ git_oid *out,+ git_repository *repo,+ const git_signature *author,+ const git_signature *committer,+ const char *notes_ref,+ const git_oid *oid,+ const char *note,+ int allow_note_overwrite) {- int error, nparents = 0;- char *target;- git_oid sha;+ int error;+ char *target = NULL; git_commit *commit = NULL;- git_reference *ref;-- if (normalize_namespace(¬es_ref, repo) < 0)- return -1;-- error = git_reference_lookup(&ref, repo, notes_ref);- if (error < 0 && error != GIT_ENOTFOUND)- return error;-- if (!error) {- assert(git_reference_type(ref) == GIT_REF_OID);-- /* lookup existing notes tree oid */-- git_oid_cpy(&sha, git_reference_oid(ref));- git_reference_free(ref);-- error = git_commit_lookup(&commit, repo, &sha);- if (error < 0)- return error;-- git_oid_cpy(&sha, git_commit_tree_oid(commit));- nparents++;- }+ git_tree *tree = NULL; target = git_oid_allocfmt(oid); GITERR_CHECK_ALLOC(target); + error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref);++ if (error < 0 && error != GIT_ENOTFOUND)+ goto cleanup;+ error = note_write(out, repo, author, committer, notes_ref,- note, nparents ? &sha : NULL, target,- nparents, &commit);+ note, tree, target, &commit, allow_note_overwrite); +cleanup: git__free(target); git_commit_free(commit);+ git_tree_free(tree); return error; } int git_note_remove(git_repository *repo, const char *notes_ref,- git_signature *author, git_signature *committer,- const git_oid *oid)+ const git_signature *author, const git_signature *committer,+ const git_oid *oid) { int error;- char *target;- git_oid sha;- git_commit *commit;- git_reference *ref;-- if (normalize_namespace(¬es_ref, repo) < 0)- return -1;-- error = git_reference_lookup(&ref, repo, notes_ref);- if (error < 0)- return error;-- assert(git_reference_type(ref) == GIT_REF_OID);-- git_oid_cpy(&sha, git_reference_oid(ref));- git_reference_free(ref);-- error = git_commit_lookup(&commit, repo, &sha);- if (error < 0)- return error;-- git_oid_cpy(&sha, git_commit_tree_oid(commit));+ char *target = NULL;+ git_commit *commit = NULL;+ git_tree *tree = NULL; target = git_oid_allocfmt(oid); GITERR_CHECK_ALLOC(target); + if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref)) < 0)+ goto cleanup;+ error = note_remove(repo, author, committer, notes_ref,- &sha, target, 1, &commit);+ tree, target, &commit); +cleanup: git__free(target); git_commit_free(commit);+ git_tree_free(tree); return error; } @@ -428,13 +508,13 @@ return note_get_default_ref(out, repo); } -const char * git_note_message(git_note *note)+const char * git_note_message(const git_note *note) { assert(note); return note->message; } -const git_oid * git_note_oid(git_note *note)+const git_oid * git_note_oid(const git_note *note) { assert(note); return ¬e->oid;@@ -451,17 +531,15 @@ static int process_entry_path( const char* entry_path,- const git_oid *note_oid,- int (*note_cb)(git_note_data *note_data, void *payload),- void *payload)+ git_oid *annotated_object_id) {- int i = 0, j = 0, error = -1, len;+ int error = -1;+ size_t i = 0, j = 0, len; git_buf buf = GIT_BUF_INIT;- git_note_data note_data; - if (git_buf_puts(&buf, entry_path) < 0)+ if ((error = git_buf_puts(&buf, entry_path)) < 0) goto cleanup;- + len = git_buf_len(&buf); while (i < len) {@@ -469,10 +547,9 @@ i++; continue; }- + if (git__fromhex(buf.ptr[i]) < 0) { /* This is not a note entry */- error = 0; goto cleanup; } @@ -488,16 +565,10 @@ if (j != GIT_OID_HEXSZ) { /* This is not a note entry */- error = 0; goto cleanup; } - if (git_oid_fromstr(¬e_data.annotated_object_oid, buf.ptr) < 0)- return -1;-- git_oid_cpy(¬e_data.blob_oid, note_oid);-- error = note_cb(¬e_data, payload);+ error = git_oid_fromstr(annotated_object_id, buf.ptr); cleanup: git_buf_free(&buf);@@ -505,44 +576,86 @@ } int git_note_foreach(- git_repository *repo,- const char *notes_ref,- int (*note_cb)(git_note_data *note_data, void *payload),- void *payload)+ git_repository *repo,+ const char *notes_ref,+ git_note_foreach_cb note_cb,+ void *payload) {- int error = -1;- git_oid tree_oid;- git_iterator *iter = NULL;- git_tree *tree = NULL;- const git_index_entry *item;+ int error;+ git_note_iterator *iter = NULL;+ git_oid note_id, annotated_id; - if (normalize_namespace(¬es_ref, repo) < 0)- return -1;+ if ((error = git_note_iterator_new(&iter, repo, notes_ref)) < 0)+ return error; - if ((error = retrieve_note_tree_oid(&tree_oid, repo, notes_ref)) < 0)- goto cleanup;+ while (!(error = git_note_next(¬e_id, &annotated_id, iter))) {+ if (note_cb(¬e_id, &annotated_id, payload)) {+ error = GIT_EUSER;+ break;+ }+ } - if (git_tree_lookup(&tree, repo, &tree_oid) < 0)- goto cleanup;+ if (error == GIT_ITEROVER)+ error = 0; - if (git_iterator_for_tree(&iter, repo, tree) < 0)- goto cleanup;+ git_note_iterator_free(iter);+ return error;+} - if (git_iterator_current(iter, &item) < 0)- goto cleanup; - while (item) {- if (process_entry_path(item->path, &item->oid, note_cb, payload) < 0)- goto cleanup;+void git_note_iterator_free(git_note_iterator *it)+{+ if (it == NULL)+ return; - if (git_iterator_advance(iter, &item) < 0)- goto cleanup;- }+ git_iterator_free(it);+} - error = 0; +int git_note_iterator_new(+ git_note_iterator **it,+ git_repository *repo,+ const char *notes_ref)+{+ int error;+ git_commit *commit = NULL;+ git_tree *tree = NULL;++ error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref);+ if (error < 0)+ goto cleanup;++ if ((error = git_iterator_for_tree(it, tree, 0, NULL, NULL)) < 0)+ git_iterator_free(*it);+ cleanup:- git_iterator_free(iter); git_tree_free(tree);+ git_commit_free(commit);++ return error;+}++int git_note_next(+ git_oid* note_id,+ git_oid* annotated_id,+ git_note_iterator *it)+{+ int error;+ const git_index_entry *item;++ if ((error = git_iterator_current(&item, it)) < 0)+ goto exit;++ if (item != NULL) {+ git_oid_cpy(note_id, &item->oid);+ error = process_entry_path(item->path, annotated_id);++ if (error >= 0)+ error = git_iterator_advance(NULL, it);+ } else {+ error = GIT_ITEROVER;+ }++exit: return error; }
libgit2/src/notes.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,6 +10,7 @@ #include "common.h" #include "git2/oid.h"+#include "git2/types.h" #define GIT_NOTES_DEFAULT_REF "refs/notes/commits"
libgit2/src/object.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -77,11 +77,63 @@ return 0; } +int git_object__from_odb_object(+ git_object **object_out,+ git_repository *repo,+ git_odb_object *odb_obj,+ git_otype type)+{+ int error;+ git_object *object = NULL;++ if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) {+ giterr_set(GITERR_INVALID, "The requested type does not match the type in the ODB");+ return GIT_ENOTFOUND;+ }++ type = odb_obj->raw.type;++ if ((error = create_object(&object, type)) < 0)+ return error;++ /* Initialize parent object */+ git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);+ object->repo = repo;++ switch (type) {+ case GIT_OBJ_COMMIT:+ error = git_commit__parse((git_commit *)object, odb_obj);+ break;++ case GIT_OBJ_TREE:+ error = git_tree__parse((git_tree *)object, odb_obj);+ break;++ case GIT_OBJ_TAG:+ error = git_tag__parse((git_tag *)object, odb_obj);+ break;++ case GIT_OBJ_BLOB:+ error = git_blob__parse((git_blob *)object, odb_obj);+ break;++ default:+ break;+ }++ if (error < 0)+ git_object__free(object);+ else+ *object_out = git_cache_try_store(&repo->objects, object);++ return error;+}+ int git_object_lookup_prefix( git_object **object_out, git_repository *repo, const git_oid *id,- unsigned int len,+ size_t len, git_otype type) { git_object *object = NULL;@@ -109,7 +161,7 @@ if (object != NULL) { if (type != GIT_OBJ_ANY && type != object->type) { git_object_free(object);- giterr_set(GITERR_ODB, "The given type does not match the type in ODB");+ giterr_set(GITERR_INVALID, "The requested type does not match the type in ODB"); return GIT_ENOTFOUND; } @@ -148,51 +200,11 @@ if (error < 0) return error; - if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) {- git_odb_object_free(odb_obj);- giterr_set(GITERR_ODB, "The given type does not match the type on the ODB");- return GIT_ENOTFOUND;- }-- type = odb_obj->raw.type;-- if (create_object(&object, type) < 0)- return -1;-- /* Initialize parent object */- git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid);- object->repo = repo;-- switch (type) {- case GIT_OBJ_COMMIT:- error = git_commit__parse((git_commit *)object, odb_obj);- break;-- case GIT_OBJ_TREE:- error = git_tree__parse((git_tree *)object, odb_obj);- break;-- case GIT_OBJ_TAG:- error = git_tag__parse((git_tag *)object, odb_obj);- break;-- case GIT_OBJ_BLOB:- error = git_blob__parse((git_blob *)object, odb_obj);- break;-- default:- break;- }+ error = git_object__from_odb_object(object_out, repo, odb_obj, type); git_odb_object_free(odb_obj); - if (error < 0) {- git_object__free(object);- return -1;- }-- *object_out = git_cache_try_store(&repo->objects, object);- return 0;+ return error; } int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) {@@ -292,42 +304,101 @@ return git_objects_table[type].size; } -int git_object__resolve_to_type(git_object **obj, git_otype type)+static int dereference_object(git_object **dereferenced, git_object *obj) {- int error = 0;- git_object *scan, *next;+ git_otype type = git_object_type(obj); - if (type == GIT_OBJ_ANY)- return 0;+ switch (type) {+ case GIT_OBJ_COMMIT:+ return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); - scan = *obj;+ case GIT_OBJ_TAG:+ return git_tag_target(dereferenced, (git_tag*)obj); - while (!error && scan && git_object_type(scan) != type) {+ case GIT_OBJ_BLOB:+ return GIT_ENOTFOUND; - switch (git_object_type(scan)) {- case GIT_OBJ_COMMIT:- {- git_tree *tree = NULL;- error = git_commit_tree(&tree, (git_commit *)scan);- next = (git_object *)tree;- break;- }+ case GIT_OBJ_TREE:+ return GIT_EAMBIGUOUS; - case GIT_OBJ_TAG:- error = git_tag_target(&next, (git_tag *)scan);- break;+ default:+ return GIT_EINVALIDSPEC;+ }+} - default:- giterr_set(GITERR_REFERENCE, "Object does not resolve to type");- error = -1;- next = NULL;- break;+static int peel_error(int error, const git_oid *oid, git_otype type)+{+ const char *type_name;+ char hex_oid[GIT_OID_HEXSZ + 1];++ type_name = git_object_type2string(type);++ git_oid_fmt(hex_oid, oid);+ hex_oid[GIT_OID_HEXSZ] = '\0';++ giterr_set(GITERR_OBJECT, "The git_object of id '%s' can not be "+ "successfully peeled into a %s (git_otype=%i).", hex_oid, type_name, type);++ return error;+}++int git_object_peel(+ git_object **peeled,+ const git_object *object,+ git_otype target_type)+{+ git_object *source, *deref = NULL;+ int error;++ if (target_type != GIT_OBJ_TAG && + target_type != GIT_OBJ_COMMIT && + target_type != GIT_OBJ_TREE && + target_type != GIT_OBJ_BLOB && + target_type != GIT_OBJ_ANY)+ return GIT_EINVALIDSPEC;++ assert(object && peeled);++ if (git_object_type(object) == target_type)+ return git_object_dup(peeled, (git_object *)object);++ source = (git_object *)object;++ while (!(error = dereference_object(&deref, source))) {++ if (source != object)+ git_object_free(source);++ if (git_object_type(deref) == target_type) {+ *peeled = deref;+ return 0; } - git_object_free(scan);- scan = next;+ if (target_type == GIT_OBJ_ANY &&+ git_object_type(deref) != git_object_type(object))+ {+ *peeled = deref;+ return 0;+ }++ source = deref;+ deref = NULL; } - *obj = scan;+ if (source != object)+ git_object_free(source);++ git_object_free(deref);++ if (error)+ error = peel_error(error, git_object_id(object), target_type);+ return error;+}++int git_object_dup(git_object **dest, git_object *source)+{+ git_cached_obj_incref(source);+ *dest = source;+ return 0; }
+ libgit2/src/object.h view
@@ -0,0 +1,32 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_object_h__+#define INCLUDE_object_h__++/** Base git object for inheritance */+struct git_object {+ git_cached_obj cached;+ git_repository *repo;+ git_otype type;+};++/* fully free the object; internal method, DO NOT EXPORT */+void git_object__free(void *object);++int git_object__from_odb_object(+ git_object **object_out,+ git_repository *repo,+ git_odb_object *odb_obj,+ git_otype type);++int git_object__resolve_to_type(git_object **obj, git_otype type);++int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);++void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid);++#endif
libgit2/src/odb.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -12,8 +12,10 @@ #include "hash.h" #include "odb.h" #include "delta-apply.h"+#include "filter.h" #include "git2/odb_backend.h"+#include "git2/oid.h" #define GIT_ALTERNATES_FILE "info/alternates" @@ -21,6 +23,8 @@ #define GIT_LOOSE_PRIORITY 2 #define GIT_PACKED_PRIORITY 1 +#define GIT_ALTERNATES_MAX_DEPTH 5+ typedef struct { git_odb_backend *backend;@@ -28,7 +32,11 @@ int is_alternate; } backend_internal; -static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)+size_t git_odb__cache_size = GIT_DEFAULT_CACHE_SIZE;++static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);++int git_odb__format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) { const char *type_str = git_object_type2string(obj_type); int len = p_snprintf(hdr, n, "%s %"PRIuZ, type_str, obj_len);@@ -49,7 +57,7 @@ if (!obj->data && obj->len != 0) return -1; - hdrlen = format_object_header(header, sizeof(header), obj->len, obj->type);+ hdrlen = git_odb__format_object_header(header, sizeof(header), obj->len, obj->type); vec[0].data = header; vec[0].len = hdrlen;@@ -105,6 +113,9 @@ void git_odb_object_free(git_odb_object *object) {+ if (object == NULL)+ return;+ git_cached_obj_decref((git_cached_obj *)object, &free_odb_object); } @@ -112,33 +123,75 @@ { int hdr_len; char hdr[64], buffer[2048];- git_hash_ctx *ctx;+ git_hash_ctx ctx;+ ssize_t read_len = 0;+ int error = 0; - hdr_len = format_object_header(hdr, sizeof(hdr), size, type);+ if (!git_object_typeisloose(type)) {+ giterr_set(GITERR_INVALID, "Invalid object type for hash");+ return -1;+ } - ctx = git_hash_new_ctx();+ if ((error = git_hash_ctx_init(&ctx)) < 0)+ return -1; - git_hash_update(ctx, hdr, hdr_len);+ hdr_len = git_odb__format_object_header(hdr, sizeof(hdr), size, type); - while (size > 0) {- ssize_t read_len = read(fd, buffer, sizeof(buffer));+ if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0)+ goto done; - if (read_len < 0) {- git_hash_free_ctx(ctx);- giterr_set(GITERR_OS, "Error reading file");- return -1;- }+ while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {+ if ((error = git_hash_update(&ctx, buffer, read_len)) < 0)+ goto done; - git_hash_update(ctx, buffer, read_len); size -= read_len; } - git_hash_final(out, ctx);- git_hash_free_ctx(ctx);+ /* If p_read returned an error code, the read obviously failed.+ * If size is not zero, the file was truncated after we originally+ * stat'd it, so we consider this a read failure too */+ if (read_len < 0 || size > 0) {+ giterr_set(GITERR_OS, "Error reading file for hashing");+ error = -1; - return 0;+ goto done;+ return -1;+ }++ error = git_hash_final(out, &ctx);++done:+ git_hash_ctx_cleanup(&ctx);+ return error; } +int git_odb__hashfd_filtered(+ git_oid *out, git_file fd, size_t size, git_otype type, git_vector *filters)+{+ int error;+ git_buf raw = GIT_BUF_INIT;+ git_buf filtered = GIT_BUF_INIT;++ if (!filters || !filters->length)+ return git_odb__hashfd(out, fd, size, type);++ /* size of data is used in header, so we have to read the whole file+ * into memory to apply filters before beginning to calculate the hash+ */++ if (!(error = git_futils_readbuffer_fd(&raw, fd, size)))+ error = git_filters_apply(&filtered, &raw, filters);++ git_buf_free(&raw);++ if (!error)+ error = git_odb_hash(out, filtered.ptr, filtered.size, type);++ git_buf_free(&filtered);++ return error;+}+ int git_odb__hashlink(git_oid *out, const char *path) { struct stat st;@@ -159,10 +212,11 @@ char *link_data; ssize_t read_len; - link_data = git__malloc((size_t)size);+ link_data = git__malloc((size_t)(size + 1)); GITERR_CHECK_ALLOC(link_data); - read_len = p_readlink(path, link_data, (size_t)(size + 1));+ read_len = p_readlink(path, link_data, (size_t)size);+ link_data[size] = '\0'; if (read_len != (ssize_t)size) { giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path); return -1;@@ -170,7 +224,7 @@ result = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB); git__free(link_data);- } else { + } else { int fd = git_futils_open_ro(path); if (fd < 0) return -1;@@ -299,7 +353,7 @@ git_odb *db = git__calloc(1, sizeof(*db)); GITERR_CHECK_ALLOC(db); - if (git_cache_init(&db->cache, GIT_DEFAULT_CACHE_SIZE, &free_odb_object) < 0 ||+ if (git_cache_init(&db->cache, git_odb__cache_size, &free_odb_object) < 0 || git_vector_init(&db->backends, 4, backend_sort_cmp) < 0) { git__free(db);@@ -317,6 +371,8 @@ assert(odb && backend); + GITERR_CHECK_VERSION(backend, GIT_ODB_BACKEND_VERSION, "git_odb_backend");+ /* Check if the backend is already owned by another ODB */ assert(!backend->odb || backend->odb == odb); @@ -347,7 +403,7 @@ return add_backend_internal(odb, backend, priority, 1); } -static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates)+static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates, int alternate_depth) { git_odb_backend *loose, *packed; @@ -361,10 +417,10 @@ add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates) < 0) return -1; - return 0;+ return load_alternates(db, objects_dir, alternate_depth); } -static int load_alternates(git_odb *odb, const char *objects_dir)+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth) { git_buf alternates_path = GIT_BUF_INIT; git_buf alternates_buf = GIT_BUF_INIT;@@ -372,6 +428,11 @@ const char *alternate; int result = 0; + /* Git reports an error, we just ignore anything deeper */+ if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) {+ return 0;+ }+ if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) return -1; @@ -392,14 +453,18 @@ if (*alternate == '\0' || *alternate == '#') continue; - /* relative path: build based on the current `objects` folder */- if (*alternate == '.') {+ /*+ * Relative path: build based on the current `objects`+ * folder. However, relative paths are only allowed in+ * the current repository.+ */+ if (*alternate == '.' && !alternate_depth) { if ((result = git_buf_joinpath(&alternates_path, objects_dir, alternate)) < 0) break; alternate = git_buf_cstr(&alternates_path); } - if ((result = add_default_backends(odb, alternate, 1)) < 0)+ if ((result = add_default_backends(odb, alternate, 1, alternate_depth + 1)) < 0) break; } @@ -409,6 +474,11 @@ return result; } +int git_odb_add_disk_alternate(git_odb *odb, const char *path)+{+ return add_default_backends(odb, path, 1, 0);+}+ int git_odb_open(git_odb **out, const char *objects_dir) { git_odb *db;@@ -420,9 +490,7 @@ if (git_odb_new(&db) < 0) return -1; - if (add_default_backends(db, objects_dir, 0) < 0 ||- load_alternates(db, objects_dir) < 0)- {+ if (add_default_backends(db, objects_dir, 0, 0) < 0) { git_odb_free(db); return -1; }@@ -433,7 +501,7 @@ static void odb_free(git_odb *db) {- unsigned int i;+ size_t i; for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i);@@ -461,8 +529,9 @@ int git_odb_exists(git_odb *db, const git_oid *id) { git_odb_object *object;- unsigned int i;+ size_t i; bool found = false;+ bool refreshed = false; assert(db && id); @@ -471,6 +540,7 @@ return (int)true; } +attempt_lookup: for (i = 0; i < db->backends.length && !found; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend;@@ -479,24 +549,51 @@ found = b->exists(b, id); } + if (!found && !refreshed) {+ if (git_odb_refresh(db) < 0) {+ giterr_clear();+ return (int)false;+ }++ refreshed = true;+ goto attempt_lookup;+ }+ return (int)found; } int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id) {- unsigned int i;+ int error;+ git_odb_object *object;++ error = git_odb__read_header_or_object(&object, len_p, type_p, db, id);++ if (object)+ git_odb_object_free(object);++ return error;+}++int git_odb__read_header_or_object(+ git_odb_object **out, size_t *len_p, git_otype *type_p,+ git_odb *db, const git_oid *id)+{+ size_t i; int error = GIT_ENOTFOUND; git_odb_object *object; - assert(db && id);+ assert(db && id && out && len_p && type_p); if ((object = git_cache_get(&db->cache, id)) != NULL) { *len_p = object->raw.len; *type_p = object->raw.type;- git_odb_object_free(object);+ *out = object; return 0; } + *out = NULL;+ for (i = 0; i < db->backends.length && error < 0; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend;@@ -517,22 +614,32 @@ *len_p = object->raw.len; *type_p = object->raw.type;- git_odb_object_free(object);+ *out = object;+ return 0; } int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) {- unsigned int i;- int error = GIT_ENOTFOUND;+ size_t i;+ int error;+ bool refreshed = false; git_rawobj raw; assert(out && db && id); + if (db->backends.length == 0) {+ giterr_set(GITERR_ODB, "Failed to lookup object: no backends loaded");+ return GIT_ENOTFOUND;+ }+ *out = git_cache_get(&db->cache, id); if (*out != NULL) return 0; +attempt_lookup:+ error = GIT_ENOTFOUND;+ for (i = 0; i < db->backends.length && error < 0; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend;@@ -541,10 +648,14 @@ error = b->read(&raw.data, &raw.len, &raw.type, b, id); } - /* TODO: If no backends are configured, this returns GIT_ENOTFOUND but- * will never have called giterr_set().- */+ if (error == GIT_ENOTFOUND && !refreshed) {+ if ((error = git_odb_refresh(db)) < 0)+ return error; + refreshed = true;+ goto attempt_lookup;+ }+ if (error && error != GIT_PASSTHROUGH) return error; @@ -553,13 +664,14 @@ } int git_odb_read_prefix(- git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len)+ git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) {- unsigned int i;+ size_t i; int error = GIT_ENOTFOUND; git_oid found_full_oid = {{0}}; git_rawobj raw;- bool found = false;+ void *data = NULL;+ bool found = false, refreshed = false; assert(out && db); @@ -575,11 +687,12 @@ return 0; } +attempt_lookup: for (i = 0; i < db->backends.length; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend; - if (b->read != NULL) {+ if (b->read_prefix != NULL) { git_oid full_oid; error = b->read_prefix(&full_oid, &raw.data, &raw.len, &raw.type, b, short_id, len); if (error == GIT_ENOTFOUND || error == GIT_PASSTHROUGH)@@ -588,13 +701,25 @@ if (error) return error; + git__free(data);+ data = raw.data;+ if (found && git_oid_cmp(&full_oid, &found_full_oid)) return git_odb__error_ambiguous("multiple matches for prefix");+ found_full_oid = full_oid; found = true; } } + if (!found && !refreshed) {+ if ((error = git_odb_refresh(db)) < 0)+ return error;++ refreshed = true;+ goto attempt_lookup;+ }+ if (!found) return git_odb__error_notfound("no match for prefix", short_id); @@ -602,15 +727,34 @@ return 0; } +int git_odb_foreach(git_odb *db, git_odb_foreach_cb cb, void *payload)+{+ unsigned int i;+ backend_internal *internal;++ git_vector_foreach(&db->backends, i, internal) {+ git_odb_backend *b = internal->backend;+ int error = b->foreach(b, cb, payload);+ if (error < 0)+ return error;+ }++ return 0;+}+ int git_odb_write( git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type) {- unsigned int i;+ size_t i; int error = GIT_ERROR; git_odb_stream *stream; assert(oid && db); + git_odb_hash(oid, data, len, type);+ if (git_odb_exists(db, oid))+ return 0;+ for (i = 0; i < db->backends.length && error < 0; ++i) { backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *b = internal->backend;@@ -643,7 +787,7 @@ int git_odb_open_wstream( git_odb_stream **stream, git_odb *db, size_t size, git_otype type) {- unsigned int i;+ size_t i; int error = GIT_ERROR; assert(stream && db);@@ -670,7 +814,7 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid) {- unsigned int i;+ size_t i; int error = GIT_ERROR; assert(stream && db);@@ -687,6 +831,56 @@ error = 0; return error;+}++int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload)+{+ size_t i;+ int error = GIT_ERROR;++ assert(out && db);++ for (i = 0; i < db->backends.length && error < 0; ++i) {+ backend_internal *internal = git_vector_get(&db->backends, i);+ git_odb_backend *b = internal->backend;++ /* we don't write in alternates! */+ if (internal->is_alternate)+ continue;++ if (b->writepack != NULL)+ error = b->writepack(out, b, progress_cb, progress_payload);+ }++ if (error == GIT_PASSTHROUGH)+ error = 0;++ return error;+}++void *git_odb_backend_malloc(git_odb_backend *backend, size_t len)+{+ GIT_UNUSED(backend);+ return git__malloc(len);+}++int git_odb_refresh(struct git_odb *db)+{+ size_t i;+ assert(db);++ for (i = 0; i < db->backends.length; ++i) {+ backend_internal *internal = git_vector_get(&db->backends, i);+ git_odb_backend *b = internal->backend;++ if (b->refresh != NULL) {+ int error = b->refresh(b);+ if (error < 0)+ return error;+ }+ }++ return 0; } int git_odb__error_notfound(const char *message, const git_oid *oid)
libgit2/src/odb.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -46,6 +46,10 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj); /*+ * Format the object header such as it would appear in the on-disk object+ */+int git_odb__format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type);+/* * Hash an open file descriptor. * This is a performance call when the contents of a fd need to be hashed, * but the fd is already open and we have the size of the contents.@@ -58,12 +62,19 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type); /*- * Hash a `path`, assuming it could be a POSIX symlink: if the path is a symlink,- * then the raw contents of the symlink will be hashed. Otherwise, this will- * fallback to `git_odb__hashfd`.+ * Hash an open file descriptor applying an array of filters+ * Acts just like git_odb__hashfd with the addition of filters...+ */+int git_odb__hashfd_filtered(+ git_oid *out, git_file fd, size_t len, git_otype type, git_vector *filters);++/*+ * Hash a `path`, assuming it could be a POSIX symlink: if the path is a+ * symlink, then the raw contents of the symlink will be hashed. Otherwise,+ * this will fallback to `git_odb__hashfd`. *- * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may only- * point to blobs.+ * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may+ * only point to blobs. */ int git_odb__hashlink(git_oid *out, const char *path); @@ -76,5 +87,13 @@ * Generate a GIT_EAMBIGUOUS error for the ODB. */ int git_odb__error_ambiguous(const char *message);++/*+ * Attempt to read object header or just return whole object if it could+ * not be read.+ */+int git_odb__read_header_or_object(+ git_odb_object **out, size_t *len_p, git_otype *type_p,+ git_odb *db, const git_oid *id); #endif
libgit2/src/odb_loose.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -42,7 +42,7 @@ typedef struct { size_t dir_len; unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */- unsigned int short_oid_len;+ size_t short_oid_len; int found; /* number of matching * objects already found */ unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of@@ -502,7 +502,7 @@ git_oid *res_oid, loose_backend *backend, const git_oid *short_oid,- unsigned int len)+ size_t len) { char *objects_dir = backend->objects_dir; size_t dir_len = strlen(objects_dir);@@ -629,7 +629,7 @@ git_otype *type_p, git_odb_backend *backend, const git_oid *short_oid,- unsigned int len)+ size_t len) { int error = 0; @@ -676,6 +676,91 @@ return !error; } +struct foreach_state {+ size_t dir_len;+ git_odb_foreach_cb cb;+ void *data;+ int cb_error;+};++GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr)+{+ int v, i = 0;+ if (strlen(ptr) != 41)+ return -1;++ if (ptr[2] != '/') {+ return -1;+ }++ v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]);+ if (v < 0)+ return -1;++ oid->id[0] = (unsigned char) v;++ ptr += 3;+ for (i = 0; i < 38; i += 2) {+ v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]);+ if (v < 0)+ return -1;++ oid->id[1 + i/2] = (unsigned char) v;+ }++ return 0;+}++static int foreach_object_dir_cb(void *_state, git_buf *path)+{+ git_oid oid;+ struct foreach_state *state = (struct foreach_state *) _state;++ if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0)+ return 0;++ if (state->cb(&oid, state->data)) {+ state->cb_error = GIT_EUSER;+ return -1;+ }++ return 0;+}++static int foreach_cb(void *_state, git_buf *path)+{+ struct foreach_state *state = (struct foreach_state *) _state;++ return git_path_direach(path, foreach_object_dir_cb, state);+}++static int loose_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data)+{+ char *objects_dir;+ int error;+ git_buf buf = GIT_BUF_INIT;+ struct foreach_state state;+ loose_backend *backend = (loose_backend *) _backend;++ assert(backend && cb);++ objects_dir = backend->objects_dir;++ git_buf_sets(&buf, objects_dir);+ git_path_to_dir(&buf);++ memset(&state, 0, sizeof(state));+ state.cb = cb;+ state.data = data;+ state.dir_len = git_buf_len(&buf);++ error = git_path_direach(&buf, foreach_cb, &state);++ git_buf_free(&buf);++ return state.cb_error ? state.cb_error : error;+}+ static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) { loose_writestream *stream = (loose_writestream *)_stream;@@ -785,7 +870,6 @@ if (git_buf_joinpath(&final_path, backend->objects_dir, "tmp_object") < 0 || git_filebuf_open(&fbuf, final_path.ptr,- GIT_FILEBUF_HASH_CONTENTS | GIT_FILEBUF_TEMPORARY | (backend->object_zlib_level << GIT_FILEBUF_DEFLATE_SHIFT)) < 0) {@@ -795,7 +879,6 @@ git_filebuf_write(&fbuf, header, header_len); git_filebuf_write(&fbuf, data, len);- git_filebuf_hash(oid, &fbuf); if (object_file_name(&final_path, backend->objects_dir, oid) < 0 || git_futils_mkpath2file(final_path.ptr, GIT_OBJECT_DIR_MODE) < 0 ||@@ -830,6 +913,7 @@ backend = git__calloc(1, sizeof(loose_backend)); GITERR_CHECK_ALLOC(backend); + backend->parent.version = GIT_ODB_BACKEND_VERSION; backend->objects_dir = git__strdup(objects_dir); GITERR_CHECK_ALLOC(backend->objects_dir); @@ -845,6 +929,7 @@ backend->parent.read_header = &loose_backend__read_header; backend->parent.writestream = &loose_backend__stream; backend->parent.exists = &loose_backend__exists;+ backend->parent.foreach = &loose_backend__foreach; backend->parent.free = &loose_backend__free; *backend_out = (git_odb_backend *)backend;
libgit2/src/odb_pack.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -24,9 +24,13 @@ git_vector packs; struct git_pack_file *last_found; char *pack_folder;- time_t pack_folder_mtime; }; +struct pack_writepack {+ struct git_odb_writepack parent;+ git_indexer_stream *indexer_stream;+};+ /** * The wonderful tale of a Packed Object lookup query * ===================================================@@ -128,13 +132,9 @@ * ***********************************************************/ -static void pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p);-static int pack_window_contains(git_mwindow *win, off_t offset);- static int packfile_sort__cb(const void *a_, const void *b_); static int packfile_load__cb(void *_data, git_buf *path);-static int packfile_refresh_all(struct pack_backend *backend); static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid);@@ -149,7 +149,7 @@ struct git_pack_entry *e, struct pack_backend *backend, const git_oid *short_oid,- unsigned int len);+ size_t len); @@ -159,23 +159,6 @@ * ***********************************************************/ -GIT_INLINE(void) pack_window_free_all(struct pack_backend *backend, struct git_pack_file *p)-{- GIT_UNUSED(backend);- git_mwindow_free_all(&p->mwf);-}--GIT_INLINE(int) pack_window_contains(git_mwindow *win, off_t offset)-{- /* We must promise at least 20 bytes (one hash) after the- * offset is available from this window, otherwise the offset- * is not actually in this window and a different window (which- * has that one hash excess) must be used. This is to support- * the object header and delta base parsing routines below.- */- return git_mwindow_contains(win, offset + 20);-}- static int packfile_sort__cb(const void *a_, const void *b_) { const struct git_pack_file *a = a_;@@ -212,7 +195,7 @@ struct pack_backend *backend = (struct pack_backend *)_data; struct git_pack_file *pack; int error;- unsigned int i;+ size_t i; if (git__suffixcmp(path->ptr, ".idx") != 0) return 0; /* not an index */@@ -233,79 +216,61 @@ return git_vector_insert(&backend->packs, pack); } -static int packfile_refresh_all(struct pack_backend *backend)+static int pack_entry_find_inner(+ struct git_pack_entry *e,+ struct pack_backend *backend,+ const git_oid *oid,+ struct git_pack_file *last_found) {- int error;- struct stat st;+ size_t i; - if (backend->pack_folder == NULL)+ if (last_found &&+ git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0) return 0; - if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode))- return git_odb__error_notfound("failed to refresh packfiles", NULL);-- if (st.st_mtime != backend->pack_folder_mtime) {- git_buf path = GIT_BUF_INIT;- git_buf_sets(&path, backend->pack_folder);-- /* reload all packs */- error = git_path_direach(&path, packfile_load__cb, (void *)backend);-- git_buf_free(&path);+ for (i = 0; i < backend->packs.length; ++i) {+ struct git_pack_file *p; - if (error < 0)- return error;+ p = git_vector_get(&backend->packs, i);+ if (p == last_found)+ continue; - git_vector_sort(&backend->packs);- backend->pack_folder_mtime = st.st_mtime;+ if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) {+ backend->last_found = p;+ return 0;+ } } - return 0;+ return -1; } static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backend, const git_oid *oid) {- int error;- unsigned int i;-- if ((error = packfile_refresh_all(backend)) < 0)- return error;+ struct git_pack_file *last_found = backend->last_found; if (backend->last_found && git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0) return 0; - for (i = 0; i < backend->packs.length; ++i) {- struct git_pack_file *p;-- p = git_vector_get(&backend->packs, i);- if (p == backend->last_found)- continue;-- if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) {- backend->last_found = p;- return 0;- }- }+ if (!pack_entry_find_inner(e, backend, oid, last_found))+ return 0; return git_odb__error_notfound("failed to find pack entry", oid); } -static int pack_entry_find_prefix(- struct git_pack_entry *e,- struct pack_backend *backend,- const git_oid *short_oid,- unsigned int len)+static unsigned pack_entry_find_prefix_inner(+ struct git_pack_entry *e,+ struct pack_backend *backend,+ const git_oid *short_oid,+ size_t len,+ struct git_pack_file *last_found) { int error;- unsigned int i;+ size_t i; unsigned found = 0; - if ((error = packfile_refresh_all(backend)) < 0)- return error;-- if (backend->last_found) {- error = git_pack_entry_find(e, backend->last_found, short_oid, len);+ if (last_found) {+ error = git_pack_entry_find(e, last_found, short_oid, len); if (error == GIT_EAMBIGUOUS) return error; if (!error)@@ -316,7 +281,7 @@ struct git_pack_file *p; p = git_vector_get(&backend->packs, i);- if (p == backend->last_found)+ if (p == last_found) continue; error = git_pack_entry_find(e, p, short_oid, len);@@ -329,6 +294,18 @@ } } + return found;+}++static int pack_entry_find_prefix(+ struct git_pack_entry *e,+ struct pack_backend *backend,+ const git_oid *short_oid,+ size_t len)+{+ struct git_pack_file *last_found = backend->last_found;+ unsigned int found = pack_entry_find_prefix_inner(e, backend, short_oid, len, last_found);+ if (!found) return git_odb__error_notfound("no matching pack entry for prefix", short_oid); else if (found > 1)@@ -345,20 +322,47 @@ * Implement the git_odb_backend API calls * ***********************************************************/+static int pack_backend__refresh(git_odb_backend *_backend)+{+ struct pack_backend *backend = (struct pack_backend *)_backend; -/*-int pack_backend__read_header(git_rawobj *obj, git_odb_backend *backend, const git_oid *oid)+ int error;+ struct stat st;+ git_buf path = GIT_BUF_INIT;++ if (backend->pack_folder == NULL)+ return 0;++ if (p_stat(backend->pack_folder, &st) < 0 || !S_ISDIR(st.st_mode))+ return git_odb__error_notfound("failed to refresh packfiles", NULL);++ git_buf_sets(&path, backend->pack_folder);++ /* reload all packs */+ error = git_path_direach(&path, packfile_load__cb, (void *)backend);++ git_buf_free(&path);++ if (error < 0)+ return error;++ git_vector_sort(&backend->packs);+ return 0;+}+++static int pack_backend__read_header(size_t *len_p, git_otype *type_p, struct git_odb_backend *backend, const git_oid *oid) {- pack_location location;+ struct git_pack_entry e;+ int error; - assert(obj && backend && oid);+ assert(len_p && type_p && backend && oid); - if (locate_packfile(&location, (struct pack_backend *)backend, oid) < 0)- return GIT_ENOTFOUND;+ if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0)+ return error; - return read_header_packed(obj, &location);+ return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); }-*/ static int pack_backend__read(void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) {@@ -384,7 +388,7 @@ git_otype *type_p, git_odb_backend *backend, const git_oid *short_oid,- unsigned int len)+ size_t len) { int error = 0; @@ -420,18 +424,101 @@ return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; } -static void pack_backend__free(git_odb_backend *_backend)+static int pack_backend__foreach(git_odb_backend *_backend, git_odb_foreach_cb cb, void *data) {+ int error;+ struct git_pack_file *p; struct pack_backend *backend; unsigned int i; + assert(_backend && cb);+ backend = (struct pack_backend *)_backend;++ /* Make sure we know about the packfiles */+ if ((error = pack_backend__refresh(_backend)) < 0)+ return error;++ git_vector_foreach(&backend->packs, i, p) {+ if ((error = git_pack_foreach_entry(p, cb, data)) < 0)+ return error;+ }++ return 0;+}++static int pack_backend__writepack_add(struct git_odb_writepack *_writepack, const void *data, size_t size, git_transfer_progress *stats)+{+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;++ assert(writepack);++ return git_indexer_stream_add(writepack->indexer_stream, data, size, stats);+}++static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_transfer_progress *stats)+{+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;++ assert(writepack);++ return git_indexer_stream_finalize(writepack->indexer_stream, stats);+}++static void pack_backend__writepack_free(struct git_odb_writepack *_writepack)+{+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;++ assert(writepack);++ git_indexer_stream_free(writepack->indexer_stream);+ git__free(writepack);+}++static int pack_backend__writepack(struct git_odb_writepack **out,+ git_odb_backend *_backend,+ git_transfer_progress_callback progress_cb,+ void *progress_payload)+{+ struct pack_backend *backend;+ struct pack_writepack *writepack;++ assert(out && _backend);++ *out = NULL;++ backend = (struct pack_backend *)_backend;++ writepack = git__calloc(1, sizeof(struct pack_writepack));+ GITERR_CHECK_ALLOC(writepack);++ if (git_indexer_stream_new(&writepack->indexer_stream,+ backend->pack_folder, progress_cb, progress_payload) < 0) {+ git__free(writepack);+ return -1;+ }++ writepack->parent.backend = _backend;+ writepack->parent.add = pack_backend__writepack_add;+ writepack->parent.commit = pack_backend__writepack_commit;+ writepack->parent.free = pack_backend__writepack_free;++ *out = (git_odb_writepack *)writepack;++ return 0;+}++static void pack_backend__free(git_odb_backend *_backend)+{+ struct pack_backend *backend;+ size_t i;+ assert(_backend); backend = (struct pack_backend *)_backend; for (i = 0; i < backend->packs.length; ++i) { struct git_pack_file *p = git_vector_get(&backend->packs, i);- packfile_free(p);+ git_packfile_free(p); } git_vector_free(&backend->packs);@@ -439,6 +526,43 @@ git__free(backend); } +int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx)+{+ struct pack_backend *backend = NULL;+ struct git_pack_file *packfile = NULL;++ if (git_packfile_check(&packfile, idx) < 0)+ return -1;++ backend = git__calloc(1, sizeof(struct pack_backend));+ GITERR_CHECK_ALLOC(backend);+ backend->parent.version = GIT_ODB_BACKEND_VERSION;++ if (git_vector_init(&backend->packs, 1, NULL) < 0)+ goto on_error;++ if (git_vector_insert(&backend->packs, packfile) < 0)+ goto on_error;++ backend->parent.read = &pack_backend__read;+ backend->parent.read_prefix = &pack_backend__read_prefix;+ backend->parent.read_header = &pack_backend__read_header;+ backend->parent.exists = &pack_backend__exists;+ backend->parent.refresh = &pack_backend__refresh;+ backend->parent.foreach = &pack_backend__foreach;+ backend->parent.free = &pack_backend__free;++ *backend_out = (git_odb_backend *)backend;++ return 0;++on_error:+ git_vector_free(&backend->packs);+ git__free(backend);+ git__free(packfile);+ return -1;+}+ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) { struct pack_backend *backend = NULL;@@ -446,6 +570,7 @@ backend = git__calloc(1, sizeof(struct pack_backend)); GITERR_CHECK_ALLOC(backend);+ backend->parent.version = GIT_ODB_BACKEND_VERSION; if (git_vector_init(&backend->packs, 8, packfile_sort__cb) < 0 || git_buf_joinpath(&path, objects_dir, "pack") < 0)@@ -455,14 +580,21 @@ } if (git_path_isdir(git_buf_cstr(&path)) == true) {+ int error;+ backend->pack_folder = git_buf_detach(&path);- backend->pack_folder_mtime = 0;+ error = pack_backend__refresh((git_odb_backend *)backend);+ if (error < 0)+ return error; } backend->parent.read = &pack_backend__read; backend->parent.read_prefix = &pack_backend__read_prefix;- backend->parent.read_header = NULL;+ backend->parent.read_header = &pack_backend__read_header; backend->parent.exists = &pack_backend__exists;+ backend->parent.refresh = &pack_backend__refresh;+ backend->parent.foreach = &pack_backend__foreach;+ backend->parent.writepack = &pack_backend__writepack; backend->parent.free = &pack_backend__free; *backend_out = (git_odb_backend *)backend;
+ libgit2/src/offmap.h view
@@ -0,0 +1,65 @@+/*+ * Copyright (C) 2012 the libgit2 contributors+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_offmap_h__+#define INCLUDE_offmap_h__++#include "common.h"+#include "git2/types.h"++#define kmalloc git__malloc+#define kcalloc git__calloc+#define krealloc git__realloc+#define kfree git__free+#include "khash.h"++__KHASH_TYPE(off, git_off_t, void *);+typedef khash_t(off) git_offmap;++#define GIT__USE_OFFMAP \+ __KHASH_IMPL(off, static kh_inline, git_off_t, void *, 1, kh_int64_hash_func, kh_int64_hash_equal);++#define git_offmap_alloc() kh_init(off)+#define git_offmap_free(h) kh_destroy(off, h), h = NULL+#define git_offmap_clear(h) kh_clear(off, h)++#define git_offmap_num_entries(h) kh_size(h)++#define git_offmap_lookup_index(h, k) kh_get(off, h, k)+#define git_offmap_valid_index(h, idx) (idx != kh_end(h))++#define git_offmap_exists(h, k) (kh_get(off, h, k) != kh_end(h))++#define git_offmap_value_at(h, idx) kh_val(h, idx)+#define git_offmap_set_value_at(h, idx, v) kh_val(h, idx) = v+#define git_offmap_delete_at(h, idx) kh_del(off, h, idx)++#define git_offmap_insert(h, key, val, rval) do { \+ khiter_t __pos = kh_put(off, h, key, &rval); \+ if (rval >= 0) { \+ if (rval == 0) kh_key(h, __pos) = key; \+ kh_val(h, __pos) = val; \+ } } while (0)++#define git_offmap_insert2(h, key, val, oldv, rval) do { \+ khiter_t __pos = kh_put(off, h, key, &rval); \+ if (rval >= 0) { \+ if (rval == 0) { \+ oldv = kh_val(h, __pos); \+ kh_key(h, __pos) = key; \+ } else { oldv = NULL; } \+ kh_val(h, __pos) = val; \+ } } while (0)++#define git_offmap_delete(h, key) do { \+ khiter_t __pos = git_offmap_lookup_index(h, key); \+ if (git_offmap_valid_index(h, __pos)) \+ git_offmap_delete_at(h, __pos); } while (0)++#define git_offmap_foreach kh_foreach+#define git_offmap_foreach_value kh_foreach_value++#endif
libgit2/src/oid.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -24,11 +24,8 @@ size_t p; int v; - if (length < 4)- return oid_error_invalid("input too short");- if (length > GIT_OID_HEXSZ)- length = GIT_OID_HEXSZ;+ return oid_error_invalid("too long"); for (p = 0; p < length - 1; p += 2) { v = (git__fromhex(str[p + 0]) << 4)@@ -54,6 +51,11 @@ return 0; } +int git_oid_fromstrp(git_oid *out, const char *str)+{+ return git_oid_fromstrn(out, str, strlen(str));+}+ int git_oid_fromstr(git_oid *out, const char *str) { return git_oid_fromstrn(out, str, GIT_OID_HEXSZ);@@ -98,11 +100,14 @@ { char str[GIT_OID_HEXSZ]; - if (!out || n == 0 || !oid)+ if (!out || n == 0) return ""; n--; /* allow room for terminating NUL */ + if (oid == NULL)+ n = 0;+ if (n > 0) { git_oid_fmt(str, oid); if (n > GIT_OID_HEXSZ)@@ -161,12 +166,7 @@ memcpy(out->id, src->id, sizeof(out->id)); } -int git_oid_cmp(const git_oid *a, const git_oid *b)-{- return memcmp(a->id, b->id, sizeof(a->id));-}--int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, unsigned int len)+int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len) { const unsigned char *a = oid_a->id; const unsigned char *b = oid_b->id;@@ -301,7 +301,7 @@ * - Each normal node points to 16 children (one for each possible * character in the oid). This is *not* stored in an array of * pointers, because in a 64-bit arch this would be sucking- * 16*sizeof(void*) = 128 bytes of memory per node, which is fucking+ * 16*sizeof(void*) = 128 bytes of memory per node, which is * insane. What we do is store Node Indexes, and use these indexes * to look up each node in the om->index array. These indexes are * signed shorts, so this limits the amount of unique OIDs that
libgit2/src/oidmap.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -28,13 +28,8 @@ return h; } -GIT_INLINE(int) hash_git_oid_equal(const git_oid *a, const git_oid *b)-{- return (memcmp(a->id, b->id, sizeof(a->id)) == 0);-}- #define GIT__USE_OIDMAP \- __KHASH_IMPL(oid, static inline, const git_oid *, void *, 1, hash_git_oid, hash_git_oid_equal)+ __KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, hash_git_oid, git_oid_equal) #define git_oidmap_alloc() kh_init(oid) #define git_oidmap_free(h) kh_destroy(oid,h), h = NULL
+ libgit2/src/pack-objects.c view
@@ -0,0 +1,1342 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "pack-objects.h"++#include "compress.h"+#include "delta.h"+#include "iterator.h"+#include "netops.h"+#include "pack.h"+#include "thread-utils.h"+#include "tree.h"++#include "git2/pack.h"+#include "git2/commit.h"+#include "git2/tag.h"+#include "git2/indexer.h"+#include "git2/config.h"++struct unpacked {+ git_pobject *object;+ void *data;+ struct git_delta_index *index;+ unsigned int depth;+};++struct tree_walk_context {+ git_packbuilder *pb;+ git_buf buf;+};++#ifdef GIT_THREADS++#define GIT_PACKBUILDER__MUTEX_OP(pb, mtx, op) do { \+ int result = git_mutex_##op(&(pb)->mtx); \+ assert(!result); \+ GIT_UNUSED(result); \+ } while (0)++#else++#define GIT_PACKBUILDER__MUTEX_OP(pb,mtx,op) GIT_UNUSED(pb)++#endif /* GIT_THREADS */++#define git_packbuilder__cache_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, lock)+#define git_packbuilder__cache_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, cache_mutex, unlock)+#define git_packbuilder__progress_lock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, lock)+#define git_packbuilder__progress_unlock(pb) GIT_PACKBUILDER__MUTEX_OP(pb, progress_mutex, unlock)++static unsigned name_hash(const char *name)+{+ unsigned c, hash = 0;++ if (!name)+ return 0;++ /*+ * This effectively just creates a sortable number from the+ * last sixteen non-whitespace characters. Last characters+ * count "most", so things that end in ".c" sort together.+ */+ while ((c = *name++) != 0) {+ if (git__isspace(c))+ continue;+ hash = (hash >> 2) + (c << 24);+ }+ return hash;+}++static int packbuilder_config(git_packbuilder *pb)+{+ git_config *config;+ int ret;+ int64_t val;++ if (git_repository_config__weakptr(&config, pb->repo) < 0)+ return -1;++#define config_get(KEY,DST,DFLT) do { \+ ret = git_config_get_int64(&val, config, KEY); \+ if (!ret) (DST) = val; \+ else if (ret == GIT_ENOTFOUND) (DST) = (DFLT); \+ else if (ret < 0) return -1; } while (0)++ config_get("pack.deltaCacheSize", pb->max_delta_cache_size,+ GIT_PACK_DELTA_CACHE_SIZE);+ config_get("pack.deltaCacheLimit", pb->cache_max_small_delta_size,+ GIT_PACK_DELTA_CACHE_LIMIT);+ config_get("pack.deltaCacheSize", pb->big_file_threshold,+ GIT_PACK_BIG_FILE_THRESHOLD);+ config_get("pack.windowMemory", pb->window_memory_limit, 0);++#undef config_get++ return 0;+}++int git_packbuilder_new(git_packbuilder **out, git_repository *repo)+{+ git_packbuilder *pb;++ *out = NULL;++ pb = git__calloc(1, sizeof(*pb));+ GITERR_CHECK_ALLOC(pb);++ pb->object_ix = git_oidmap_alloc();++ if (!pb->object_ix)+ goto on_error;++ pb->repo = repo;+ pb->nr_threads = 1; /* do not spawn any thread by default */++ if (git_hash_ctx_init(&pb->ctx) < 0 ||+ git_repository_odb(&pb->odb, repo) < 0 ||+ packbuilder_config(pb) < 0)+ goto on_error;++#ifdef GIT_THREADS++ if (git_mutex_init(&pb->cache_mutex) ||+ git_mutex_init(&pb->progress_mutex) ||+ git_cond_init(&pb->progress_cond))+ goto on_error;++#endif++ *out = pb;+ return 0;++on_error:+ git_packbuilder_free(pb);+ return -1;+}++unsigned int git_packbuilder_set_threads(git_packbuilder *pb, unsigned int n)+{+ assert(pb);++#ifdef GIT_THREADS+ pb->nr_threads = n;+#else+ GIT_UNUSED(n);+ assert(1 == pb->nr_threads);+#endif++ return pb->nr_threads;+}++static void rehash(git_packbuilder *pb)+{+ git_pobject *po;+ khiter_t pos;+ unsigned int i;+ int ret;++ kh_clear(oid, pb->object_ix);+ for (i = 0, po = pb->object_list; i < pb->nr_objects; i++, po++) {+ pos = kh_put(oid, pb->object_ix, &po->id, &ret);+ kh_value(pb->object_ix, pos) = po;+ }+}++int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid,+ const char *name)+{+ git_pobject *po;+ khiter_t pos;+ int ret;++ assert(pb && oid);++ /* If the object already exists in the hash table, then we don't+ * have any work to do */+ pos = kh_get(oid, pb->object_ix, oid);+ if (pos != kh_end(pb->object_ix))+ return 0;++ if (pb->nr_objects >= pb->nr_alloc) {+ pb->nr_alloc = (pb->nr_alloc + 1024) * 3 / 2;+ pb->object_list = git__realloc(pb->object_list,+ pb->nr_alloc * sizeof(*po));+ GITERR_CHECK_ALLOC(pb->object_list);+ rehash(pb);+ }++ po = pb->object_list + pb->nr_objects;+ memset(po, 0x0, sizeof(*po));++ if (git_odb_read_header(&po->size, &po->type, pb->odb, oid) < 0)+ return -1;++ pb->nr_objects++;+ git_oid_cpy(&po->id, oid);+ po->hash = name_hash(name);++ pos = kh_put(oid, pb->object_ix, &po->id, &ret);+ assert(ret != 0);+ kh_value(pb->object_ix, pos) = po;++ pb->done = false;+ return 0;+}++/*+ * The per-object header is a pretty dense thing, which is+ * - first byte: low four bits are "size",+ * then three bits of "type",+ * with the high bit being "size continues".+ * - each byte afterwards: low seven bits are size continuation,+ * with the high bit being "size continues"+ */+static int gen_pack_object_header(+ unsigned char *hdr,+ unsigned long size,+ git_otype type)+{+ unsigned char *hdr_base;+ unsigned char c;++ assert(type >= GIT_OBJ_COMMIT && type <= GIT_OBJ_REF_DELTA);++ /* TODO: add support for chunked objects; see git.git 6c0d19b1 */++ c = (unsigned char)((type << 4) | (size & 15));+ size >>= 4;+ hdr_base = hdr;++ while (size) {+ *hdr++ = c | 0x80;+ c = size & 0x7f;+ size >>= 7;+ }+ *hdr++ = c;++ return (int)(hdr - hdr_base);+}++static int get_delta(void **out, git_odb *odb, git_pobject *po)+{+ git_odb_object *src = NULL, *trg = NULL;+ unsigned long delta_size;+ void *delta_buf;++ *out = NULL;++ if (git_odb_read(&src, odb, &po->delta->id) < 0 ||+ git_odb_read(&trg, odb, &po->id) < 0)+ goto on_error;++ delta_buf = git_delta(+ git_odb_object_data(src), (unsigned long)git_odb_object_size(src),+ git_odb_object_data(trg), (unsigned long)git_odb_object_size(trg),+ &delta_size, 0);++ if (!delta_buf || delta_size != po->delta_size) {+ giterr_set(GITERR_INVALID, "Delta size changed");+ goto on_error;+ }++ *out = delta_buf;++ git_odb_object_free(src);+ git_odb_object_free(trg);+ return 0;++on_error:+ git_odb_object_free(src);+ git_odb_object_free(trg);+ return -1;+}++static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po)+{+ git_odb_object *obj = NULL;+ git_buf zbuf = GIT_BUF_INIT;+ git_otype type;+ unsigned char hdr[10];+ unsigned int hdr_len;+ unsigned long size;+ void *data;++ if (po->delta) {+ if (po->delta_data)+ data = po->delta_data;+ else if (get_delta(&data, pb->odb, po) < 0)+ goto on_error;+ size = po->delta_size;+ type = GIT_OBJ_REF_DELTA;+ } else {+ if (git_odb_read(&obj, pb->odb, &po->id))+ goto on_error;++ data = (void *)git_odb_object_data(obj);+ size = (unsigned long)git_odb_object_size(obj);+ type = git_odb_object_type(obj);+ }++ /* Write header */+ hdr_len = gen_pack_object_header(hdr, size, type);++ if (git_buf_put(buf, (char *)hdr, hdr_len) < 0)+ goto on_error;++ if (git_hash_update(&pb->ctx, hdr, hdr_len) < 0)+ goto on_error;++ if (type == GIT_OBJ_REF_DELTA) {+ if (git_buf_put(buf, (char *)po->delta->id.id, GIT_OID_RAWSZ) < 0 ||+ git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ) < 0)+ goto on_error;+ }++ /* Write data */+ if (po->z_delta_size)+ size = po->z_delta_size;+ else if (git__compress(&zbuf, data, size) < 0)+ goto on_error;+ else {+ if (po->delta)+ git__free(data);+ data = zbuf.ptr;+ size = (unsigned long)zbuf.size;+ }++ if (git_buf_put(buf, data, size) < 0 ||+ git_hash_update(&pb->ctx, data, size) < 0)+ goto on_error;++ if (po->delta_data)+ git__free(po->delta_data);++ git_odb_object_free(obj);+ git_buf_free(&zbuf);++ pb->nr_written++;+ return 0;++on_error:+ git_odb_object_free(obj);+ git_buf_free(&zbuf);+ return -1;+}++enum write_one_status {+ WRITE_ONE_SKIP = -1, /* already written */+ WRITE_ONE_BREAK = 0, /* writing this will bust the limit; not written */+ WRITE_ONE_WRITTEN = 1, /* normal */+ WRITE_ONE_RECURSIVE = 2 /* already scheduled to be written */+};++static int write_one(git_buf *buf, git_packbuilder *pb, git_pobject *po,+ enum write_one_status *status)+{+ if (po->recursing) {+ *status = WRITE_ONE_RECURSIVE;+ return 0;+ } else if (po->written) {+ *status = WRITE_ONE_SKIP;+ return 0;+ }++ if (po->delta) {+ po->recursing = 1;+ if (write_one(buf, pb, po->delta, status) < 0)+ return -1;+ switch (*status) {+ case WRITE_ONE_RECURSIVE:+ /* we cannot depend on this one */+ po->delta = NULL;+ break;+ default:+ break;+ }+ }++ po->written = 1;+ po->recursing = 0;+ return write_object(buf, pb, po);+}++GIT_INLINE(void) add_to_write_order(git_pobject **wo, unsigned int *endp,+ git_pobject *po)+{+ if (po->filled)+ return;+ wo[(*endp)++] = po;+ po->filled = 1;+}++static void add_descendants_to_write_order(git_pobject **wo, unsigned int *endp,+ git_pobject *po)+{+ int add_to_order = 1;+ while (po) {+ if (add_to_order) {+ git_pobject *s;+ /* add this node... */+ add_to_write_order(wo, endp, po);+ /* all its siblings... */+ for (s = po->delta_sibling; s; s = s->delta_sibling) {+ add_to_write_order(wo, endp, s);+ }+ }+ /* drop down a level to add left subtree nodes if possible */+ if (po->delta_child) {+ add_to_order = 1;+ po = po->delta_child;+ } else {+ add_to_order = 0;+ /* our sibling might have some children, it is next */+ if (po->delta_sibling) {+ po = po->delta_sibling;+ continue;+ }+ /* go back to our parent node */+ po = po->delta;+ while (po && !po->delta_sibling) {+ /* we're on the right side of a subtree, keep+ * going up until we can go right again */+ po = po->delta;+ }+ if (!po) {+ /* done- we hit our original root node */+ return;+ }+ /* pass it off to sibling at this level */+ po = po->delta_sibling;+ }+ };+}++static void add_family_to_write_order(git_pobject **wo, unsigned int *endp,+ git_pobject *po)+{+ git_pobject *root;++ for (root = po; root->delta; root = root->delta)+ ; /* nothing */+ add_descendants_to_write_order(wo, endp, root);+}++static int cb_tag_foreach(const char *name, git_oid *oid, void *data)+{+ git_packbuilder *pb = data;+ git_pobject *po;+ khiter_t pos;++ GIT_UNUSED(name);++ pos = kh_get(oid, pb->object_ix, oid);+ if (pos == kh_end(pb->object_ix))+ return 0;++ po = kh_value(pb->object_ix, pos);+ po->tagged = 1;++ /* TODO: peel objects */++ return 0;+}++static git_pobject **compute_write_order(git_packbuilder *pb)+{+ unsigned int i, wo_end, last_untagged;++ git_pobject **wo = git__malloc(sizeof(*wo) * pb->nr_objects);++ for (i = 0; i < pb->nr_objects; i++) {+ git_pobject *po = pb->object_list + i;+ po->tagged = 0;+ po->filled = 0;+ po->delta_child = NULL;+ po->delta_sibling = NULL;+ }++ /*+ * Fully connect delta_child/delta_sibling network.+ * Make sure delta_sibling is sorted in the original+ * recency order.+ */+ for (i = pb->nr_objects; i > 0;) {+ git_pobject *po = &pb->object_list[--i];+ if (!po->delta)+ continue;+ /* Mark me as the first child */+ po->delta_sibling = po->delta->delta_child;+ po->delta->delta_child = po;+ }++ /*+ * Mark objects that are at the tip of tags.+ */+ if (git_tag_foreach(pb->repo, &cb_tag_foreach, pb) < 0)+ return NULL;++ /*+ * Give the objects in the original recency order until+ * we see a tagged tip.+ */+ for (i = wo_end = 0; i < pb->nr_objects; i++) {+ git_pobject *po = pb->object_list + i;+ if (po->tagged)+ break;+ add_to_write_order(wo, &wo_end, po);+ }+ last_untagged = i;++ /*+ * Then fill all the tagged tips.+ */+ for (; i < pb->nr_objects; i++) {+ git_pobject *po = pb->object_list + i;+ if (po->tagged)+ add_to_write_order(wo, &wo_end, po);+ }++ /*+ * And then all remaining commits and tags.+ */+ for (i = last_untagged; i < pb->nr_objects; i++) {+ git_pobject *po = pb->object_list + i;+ if (po->type != GIT_OBJ_COMMIT &&+ po->type != GIT_OBJ_TAG)+ continue;+ add_to_write_order(wo, &wo_end, po);+ }++ /*+ * And then all the trees.+ */+ for (i = last_untagged; i < pb->nr_objects; i++) {+ git_pobject *po = pb->object_list + i;+ if (po->type != GIT_OBJ_TREE)+ continue;+ add_to_write_order(wo, &wo_end, po);+ }++ /*+ * Finally all the rest in really tight order+ */+ for (i = last_untagged; i < pb->nr_objects; i++) {+ git_pobject *po = pb->object_list + i;+ if (!po->filled)+ add_family_to_write_order(wo, &wo_end, po);+ }++ if (wo_end != pb->nr_objects) {+ giterr_set(GITERR_INVALID, "invalid write order");+ return NULL;+ }++ return wo;+}++static int write_pack(git_packbuilder *pb,+ int (*cb)(void *buf, size_t size, void *data),+ void *data)+{+ git_pobject **write_order;+ git_pobject *po;+ git_buf buf = GIT_BUF_INIT;+ enum write_one_status status;+ struct git_pack_header ph;+ unsigned int i = 0;++ write_order = compute_write_order(pb);+ if (write_order == NULL)+ goto on_error;++ /* Write pack header */+ ph.hdr_signature = htonl(PACK_SIGNATURE);+ ph.hdr_version = htonl(PACK_VERSION);+ ph.hdr_entries = htonl(pb->nr_objects);++ if (cb(&ph, sizeof(ph), data) < 0)+ goto on_error;++ if (git_hash_update(&pb->ctx, &ph, sizeof(ph)) < 0)+ goto on_error;++ pb->nr_remaining = pb->nr_objects;+ do {+ pb->nr_written = 0;+ for ( ; i < pb->nr_objects; ++i) {+ po = write_order[i];+ if (write_one(&buf, pb, po, &status) < 0)+ goto on_error;+ if (cb(buf.ptr, buf.size, data) < 0)+ goto on_error;+ git_buf_clear(&buf);+ }++ pb->nr_remaining -= pb->nr_written;+ } while (pb->nr_remaining && i < pb->nr_objects);++ git__free(write_order);+ git_buf_free(&buf);++ if (git_hash_final(&pb->pack_oid, &pb->ctx) < 0)+ goto on_error;++ return cb(pb->pack_oid.id, GIT_OID_RAWSZ, data);++on_error:+ git__free(write_order);+ git_buf_free(&buf);+ return -1;+}++static int write_pack_buf(void *buf, size_t size, void *data)+{+ git_buf *b = (git_buf *)data;+ return git_buf_put(b, buf, size);+}++static int write_pack_to_file(void *buf, size_t size, void *data)+{+ git_filebuf *file = (git_filebuf *)data;+ return git_filebuf_write(file, buf, size);+}++static int write_pack_file(git_packbuilder *pb, const char *path)+{+ git_filebuf file = GIT_FILEBUF_INIT;++ if (git_filebuf_open(&file, path, 0) < 0 ||+ write_pack(pb, &write_pack_to_file, &file) < 0 ||+ git_filebuf_commit(&file, GIT_PACK_FILE_MODE) < 0) {+ git_filebuf_cleanup(&file);+ return -1;+ }++ return 0;+}++static int type_size_sort(const void *_a, const void *_b)+{+ const git_pobject *a = (git_pobject *)_a;+ const git_pobject *b = (git_pobject *)_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;+ /*+ * TODO+ *+ if (a->preferred_base > b->preferred_base)+ return -1;+ if (a->preferred_base < b->preferred_base)+ return 1;+ */+ if (a->size > b->size)+ return -1;+ if (a->size < b->size)+ return 1;+ return a < b ? -1 : (a > b); /* newest first */+}++static int delta_cacheable(git_packbuilder *pb, unsigned long src_size,+ unsigned long trg_size, unsigned long delta_size)+{+ if (pb->max_delta_cache_size &&+ pb->delta_cache_size + delta_size > pb->max_delta_cache_size)+ return 0;++ if (delta_size < pb->cache_max_small_delta_size)+ return 1;++ /* cache delta, if objects are large enough compared to delta size */+ if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))+ return 1;++ return 0;+}++static int try_delta(git_packbuilder *pb, struct unpacked *trg,+ struct unpacked *src, unsigned int max_depth,+ unsigned long *mem_usage, int *ret)+{+ git_pobject *trg_object = trg->object;+ git_pobject *src_object = src->object;+ git_odb_object *obj;+ unsigned long trg_size, src_size, delta_size,+ sizediff, max_size, sz;+ unsigned int ref_depth;+ void *delta_buf;++ /* Don't bother doing diffs between different types */+ if (trg_object->type != src_object->type) {+ *ret = -1;+ return 0;+ }++ *ret = 0;++ /* TODO: support reuse-delta */++ /* Let's not bust the allowed depth. */+ if (src->depth >= max_depth)+ return 0;++ /* Now some size filtering heuristics. */+ trg_size = (unsigned long)trg_object->size;+ if (!trg_object->delta) {+ max_size = trg_size/2 - 20;+ ref_depth = 1;+ } else {+ max_size = trg_object->delta_size;+ ref_depth = trg->depth;+ }++ max_size = (uint64_t)max_size * (max_depth - src->depth) /+ (max_depth - ref_depth + 1);+ if (max_size == 0)+ return 0;++ src_size = (unsigned long)src_object->size;+ sizediff = src_size < trg_size ? trg_size - src_size : 0;+ if (sizediff >= max_size)+ return 0;+ if (trg_size < src_size / 32)+ return 0;++ /* Load data if not already done */+ if (!trg->data) {+ if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0)+ return -1;++ sz = (unsigned long)git_odb_object_size(obj);+ trg->data = git__malloc(sz);+ GITERR_CHECK_ALLOC(trg->data);+ memcpy(trg->data, git_odb_object_data(obj), sz);++ git_odb_object_free(obj);++ if (sz != trg_size) {+ giterr_set(GITERR_INVALID,+ "Inconsistent target object length");+ return -1;+ }++ *mem_usage += sz;+ }+ if (!src->data) {+ if (git_odb_read(&obj, pb->odb, &src_object->id) < 0)+ return -1;++ sz = (unsigned long)git_odb_object_size(obj);+ src->data = git__malloc(sz);+ GITERR_CHECK_ALLOC(src->data);+ memcpy(src->data, git_odb_object_data(obj), sz);++ git_odb_object_free(obj);++ if (sz != src_size) {+ giterr_set(GITERR_INVALID,+ "Inconsistent source object length");+ return -1;+ }++ *mem_usage += sz;+ }+ if (!src->index) {+ src->index = git_delta_create_index(src->data, src_size);+ if (!src->index)+ return 0; /* suboptimal pack - out of memory */++ *mem_usage += git_delta_sizeof_index(src->index);+ }++ delta_buf = git_delta_create(src->index, trg->data, trg_size,+ &delta_size, max_size);+ if (!delta_buf)+ return 0;++ if (trg_object->delta) {+ /* Prefer only shallower same-sized deltas. */+ if (delta_size == trg_object->delta_size &&+ src->depth + 1 >= trg->depth) {+ git__free(delta_buf);+ return 0;+ }+ }++ git_packbuilder__cache_lock(pb);+ if (trg_object->delta_data) {+ git__free(trg_object->delta_data);+ pb->delta_cache_size -= trg_object->delta_size;+ trg_object->delta_data = NULL;+ }+ if (delta_cacheable(pb, src_size, trg_size, delta_size)) {+ pb->delta_cache_size += delta_size;+ git_packbuilder__cache_unlock(pb);++ trg_object->delta_data = git__realloc(delta_buf, delta_size);+ GITERR_CHECK_ALLOC(trg_object->delta_data);+ } else {+ /* create delta when writing the pack */+ git_packbuilder__cache_unlock(pb);+ git__free(delta_buf);+ }++ trg_object->delta = src_object;+ trg_object->delta_size = delta_size;+ trg->depth = src->depth + 1;++ *ret = 1;+ return 0;+}++static unsigned int check_delta_limit(git_pobject *me, unsigned int n)+{+ git_pobject *child = me->delta_child;+ unsigned int m = n;++ while (child) {+ unsigned int c = check_delta_limit(child, n + 1);+ if (m < c)+ m = c;+ child = child->delta_sibling;+ }+ return m;+}++static unsigned long free_unpacked(struct unpacked *n)+{+ unsigned long freed_mem = git_delta_sizeof_index(n->index);+ git_delta_free_index(n->index);+ n->index = NULL;+ if (n->data) {+ freed_mem += (unsigned long)n->object->size;+ git__free(n->data);+ n->data = NULL;+ }+ n->object = NULL;+ n->depth = 0;+ return freed_mem;+}++static int find_deltas(git_packbuilder *pb, git_pobject **list,+ unsigned int *list_size, unsigned int window,+ unsigned int depth)+{+ git_pobject *po;+ git_buf zbuf = GIT_BUF_INIT;+ struct unpacked *array;+ uint32_t idx = 0, count = 0;+ unsigned long mem_usage = 0;+ unsigned int i;+ int error = -1;++ array = git__calloc(window, sizeof(struct unpacked));+ GITERR_CHECK_ALLOC(array);++ for (;;) {+ struct unpacked *n = array + idx;+ unsigned int max_depth;+ int j, best_base = -1;++ git_packbuilder__progress_lock(pb);+ if (!*list_size) {+ git_packbuilder__progress_unlock(pb);+ break;+ }++ po = *list++;+ (*list_size)--;+ git_packbuilder__progress_unlock(pb);++ mem_usage -= free_unpacked(n);+ n->object = po;++ while (pb->window_memory_limit &&+ mem_usage > pb->window_memory_limit &&+ count > 1) {+ uint32_t tail = (idx + window - count) % window;+ mem_usage -= free_unpacked(array + tail);+ count--;+ }++ /*+ * If the current object is at pack edge, take the depth the+ * objects that depend on the current object into account+ * otherwise they would become too deep.+ */+ max_depth = depth;+ if (po->delta_child) {+ max_depth -= check_delta_limit(po, 0);+ if (max_depth <= 0)+ goto next;+ }++ j = window;+ while (--j > 0) {+ int ret;+ uint32_t other_idx = idx + j;+ struct unpacked *m;++ if (other_idx >= window)+ other_idx -= window;++ m = array + other_idx;+ if (!m->object)+ break;++ if (try_delta(pb, n, m, max_depth, &mem_usage, &ret) < 0)+ goto on_error;+ if (ret < 0)+ break;+ else if (ret > 0)+ best_base = other_idx;+ }++ /*+ * If we decided to cache the delta data, then it is best+ * to compress it right away. First because we have to do+ * it anyway, and doing it here while we're threaded will+ * save a lot of time in the non threaded write phase,+ * as well as allow for caching more deltas within+ * the same cache size limit.+ * ...+ * But only if not writing to stdout, since in that case+ * the network is most likely throttling writes anyway,+ * and therefore it is best to go to the write phase ASAP+ * instead, as we can afford spending more time compressing+ * between writes at that moment.+ */+ if (po->delta_data) {+ if (git__compress(&zbuf, po->delta_data, po->delta_size) < 0)+ goto on_error;++ git__free(po->delta_data);+ po->delta_data = git__malloc(zbuf.size);+ GITERR_CHECK_ALLOC(po->delta_data);++ memcpy(po->delta_data, zbuf.ptr, zbuf.size);+ po->z_delta_size = (unsigned long)zbuf.size;+ git_buf_clear(&zbuf);++ git_packbuilder__cache_lock(pb);+ pb->delta_cache_size -= po->delta_size;+ pb->delta_cache_size += po->z_delta_size;+ git_packbuilder__cache_unlock(pb);+ }++ /*+ * If we made n a delta, and if n is already at max+ * depth, leaving it in the window is pointless. we+ * should evict it first.+ */+ if (po->delta && max_depth <= n->depth)+ continue;++ /*+ * Move the best delta base up in the window, after the+ * currently deltified object, to keep it longer. It will+ * be the first base object to be attempted next.+ */+ if (po->delta) {+ struct unpacked swap = array[best_base];+ int dist = (window + idx - best_base) % window;+ int dst = best_base;+ while (dist--) {+ int src = (dst + 1) % window;+ array[dst] = array[src];+ dst = src;+ }+ array[dst] = swap;+ }++ next:+ idx++;+ if (count + 1 < window)+ count++;+ if (idx >= window)+ idx = 0;+ }+ error = 0;++on_error:+ for (i = 0; i < window; ++i) {+ git__free(array[i].index);+ git__free(array[i].data);+ }+ git__free(array);+ git_buf_free(&zbuf);++ return error;+}++#ifdef GIT_THREADS++struct thread_params {+ git_thread thread;+ git_packbuilder *pb;++ git_pobject **list;++ git_cond cond;+ git_mutex mutex;++ unsigned int list_size;+ unsigned int remaining;++ int window;+ int depth;+ int working;+ int data_ready;+};++static void *threaded_find_deltas(void *arg)+{+ struct thread_params *me = arg;++ while (me->remaining) {+ if (find_deltas(me->pb, me->list, &me->remaining,+ me->window, me->depth) < 0) {+ ; /* TODO */+ }++ git_packbuilder__progress_lock(me->pb);+ me->working = 0;+ git_cond_signal(&me->pb->progress_cond);+ git_packbuilder__progress_unlock(me->pb);++ if (git_mutex_lock(&me->mutex)) {+ giterr_set(GITERR_THREAD, "unable to lock packfile condition mutex");+ return NULL;+ }++ while (!me->data_ready)+ git_cond_wait(&me->cond, &me->mutex);++ /*+ * We must not set ->data_ready before we wait on the+ * condition because the main thread may have set it to 1+ * before we get here. In order to be sure that new+ * work is available if we see 1 in ->data_ready, it+ * was initialized to 0 before this thread was spawned+ * and we reset it to 0 right away.+ */+ me->data_ready = 0;+ git_mutex_unlock(&me->mutex);+ }+ /* leave ->working 1 so that this doesn't get more work assigned */+ return NULL;+}++static int ll_find_deltas(git_packbuilder *pb, git_pobject **list,+ unsigned int list_size, unsigned int window,+ unsigned int depth)+{+ struct thread_params *p;+ int i, ret, active_threads = 0;++ if (!pb->nr_threads)+ pb->nr_threads = git_online_cpus();++ if (pb->nr_threads <= 1) {+ find_deltas(pb, list, &list_size, window, depth);+ return 0;+ }++ p = git__malloc(pb->nr_threads * sizeof(*p));+ GITERR_CHECK_ALLOC(p);++ /* Partition the work among the threads */+ for (i = 0; i < pb->nr_threads; ++i) {+ unsigned sub_size = list_size / (pb->nr_threads - i);++ /* don't use too small segments or no deltas will be found */+ if (sub_size < 2*window && i+1 < pb->nr_threads)+ sub_size = 0;++ p[i].pb = pb;+ p[i].window = window;+ p[i].depth = depth;+ p[i].working = 1;+ p[i].data_ready = 0;++ /* try to split chunks on "path" boundaries */+ while (sub_size && sub_size < list_size &&+ list[sub_size]->hash &&+ list[sub_size]->hash == list[sub_size-1]->hash)+ sub_size++;++ p[i].list = list;+ p[i].list_size = sub_size;+ p[i].remaining = sub_size;++ list += sub_size;+ list_size -= sub_size;+ }++ /* Start work threads */+ for (i = 0; i < pb->nr_threads; ++i) {+ if (!p[i].list_size)+ continue;++ git_mutex_init(&p[i].mutex);+ git_cond_init(&p[i].cond);++ ret = git_thread_create(&p[i].thread, NULL,+ threaded_find_deltas, &p[i]);+ if (ret) {+ giterr_set(GITERR_THREAD, "unable to create thread");+ return -1;+ }+ active_threads++;+ }++ /*+ * Now let's wait for work completion. Each time a thread is done+ * with its work, we steal half of the remaining work from the+ * thread with the largest number of unprocessed objects and give+ * it to that newly idle thread. This ensure good load balancing+ * until the remaining object list segments are simply too short+ * to be worth splitting anymore.+ */+ while (active_threads) {+ struct thread_params *target = NULL;+ struct thread_params *victim = NULL;+ unsigned sub_size = 0;++ /* Start by locating a thread that has transitioned its+ * 'working' flag from 1 -> 0. This indicates that it is+ * ready to receive more work using our work-stealing+ * algorithm. */+ git_packbuilder__progress_lock(pb);+ for (;;) {+ for (i = 0; !target && i < pb->nr_threads; i++)+ if (!p[i].working)+ target = &p[i];+ if (target)+ break;+ git_cond_wait(&pb->progress_cond, &pb->progress_mutex);+ }++ /* At this point we hold the progress lock and have located+ * a thread to receive more work. We still need to locate a+ * thread from which to steal work (the victim). */+ for (i = 0; i < pb->nr_threads; i++)+ if (p[i].remaining > 2*window &&+ (!victim || victim->remaining < p[i].remaining))+ victim = &p[i];++ if (victim) {+ sub_size = victim->remaining / 2;+ list = victim->list + victim->list_size - sub_size;+ while (sub_size && list[0]->hash &&+ list[0]->hash == list[-1]->hash) {+ list++;+ sub_size--;+ }+ if (!sub_size) {+ /*+ * It is possible for some "paths" to have+ * so many objects that no hash boundary+ * might be found. Let's just steal the+ * exact half in that case.+ */+ sub_size = victim->remaining / 2;+ list -= sub_size;+ }+ target->list = list;+ victim->list_size -= sub_size;+ victim->remaining -= sub_size;+ }+ target->list_size = sub_size;+ target->remaining = sub_size;+ target->working = 1;+ git_packbuilder__progress_unlock(pb);++ if (git_mutex_lock(&target->mutex)) {+ giterr_set(GITERR_THREAD, "unable to lock packfile condition mutex");+ git__free(p);+ return -1;+ }++ target->data_ready = 1;+ git_cond_signal(&target->cond);+ git_mutex_unlock(&target->mutex);++ if (!sub_size) {+ git_thread_join(target->thread, NULL);+ git_cond_free(&target->cond);+ git_mutex_free(&target->mutex);+ active_threads--;+ }+ }++ git__free(p);+ return 0;+}++#else+#define ll_find_deltas(pb, l, ls, w, d) find_deltas(pb, l, &ls, w, d)+#endif++static int prepare_pack(git_packbuilder *pb)+{+ git_pobject **delta_list;+ unsigned int i, n = 0;++ if (pb->nr_objects == 0 || pb->done)+ return 0; /* nothing to do */++ delta_list = git__malloc(pb->nr_objects * sizeof(*delta_list));+ GITERR_CHECK_ALLOC(delta_list);++ for (i = 0; i < pb->nr_objects; ++i) {+ git_pobject *po = pb->object_list + i;++ /* Make sure the item is within our size limits */+ if (po->size < 50 || po->size > pb->big_file_threshold)+ continue;++ delta_list[n++] = po;+ }++ if (n > 1) {+ git__tsort((void **)delta_list, n, type_size_sort);+ if (ll_find_deltas(pb, delta_list, n,+ GIT_PACK_WINDOW + 1,+ GIT_PACK_DEPTH) < 0) {+ git__free(delta_list);+ return -1;+ }+ }++ pb->done = true;+ git__free(delta_list);+ return 0;+}++#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }++int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload)+{+ PREPARE_PACK;+ return write_pack(pb, cb, payload);+}++int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb)+{+ PREPARE_PACK;+ return write_pack(pb, &write_pack_buf, buf);+}++int git_packbuilder_write(git_packbuilder *pb, const char *path)+{+ PREPARE_PACK;+ return write_pack_file(pb, path);+}++#undef PREPARE_PACK++static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *payload)+{+ struct tree_walk_context *ctx = payload;++ /* A commit inside a tree represents a submodule commit and should be skipped. */+ if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT)+ return 0;++ if (git_buf_sets(&ctx->buf, root) < 0 ||+ git_buf_puts(&ctx->buf, git_tree_entry_name(entry)) < 0)+ return -1;++ return git_packbuilder_insert(ctx->pb,+ git_tree_entry_id(entry),+ git_buf_cstr(&ctx->buf));+}++int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid)+{+ git_tree *tree;+ struct tree_walk_context context = { pb, GIT_BUF_INIT };++ if (git_tree_lookup(&tree, pb->repo, oid) < 0 ||+ git_packbuilder_insert(pb, oid, NULL) < 0)+ return -1;++ if (git_tree_walk(tree, GIT_TREEWALK_PRE, cb_tree_walk, &context) < 0) {+ git_tree_free(tree);+ git_buf_free(&context.buf);+ return -1;+ }++ git_tree_free(tree);+ git_buf_free(&context.buf);+ return 0;+}++uint32_t git_packbuilder_object_count(git_packbuilder *pb)+{+ return pb->nr_objects;+}++uint32_t git_packbuilder_written(git_packbuilder *pb)+{+ return pb->nr_written;+}++void git_packbuilder_free(git_packbuilder *pb)+{+ if (pb == NULL)+ return;++#ifdef GIT_THREADS++ git_mutex_free(&pb->cache_mutex);+ git_mutex_free(&pb->progress_mutex);+ git_cond_free(&pb->progress_cond);++#endif++ if (pb->odb)+ git_odb_free(pb->odb);++ if (pb->object_ix)+ git_oidmap_free(pb->object_ix);++ if (pb->object_list)+ git__free(pb->object_list);++ git_hash_ctx_cleanup(&pb->ctx);++ git__free(pb);+}
+ libgit2/src/pack-objects.h view
@@ -0,0 +1,87 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#ifndef INCLUDE_pack_objects_h__+#define INCLUDE_pack_objects_h__++#include "common.h"++#include "buffer.h"+#include "hash.h"+#include "oidmap.h"+#include "netops.h"++#include "git2/oid.h"++#define GIT_PACK_WINDOW 10 /* number of objects to possibly delta against */+#define GIT_PACK_DEPTH 50 /* max delta depth */+#define GIT_PACK_DELTA_CACHE_SIZE (256 * 1024 * 1024)+#define GIT_PACK_DELTA_CACHE_LIMIT 1000+#define GIT_PACK_BIG_FILE_THRESHOLD (512 * 1024 * 1024)++typedef struct git_pobject {+ git_oid id;+ git_otype type;+ git_off_t offset;++ size_t size;++ unsigned int hash; /* name hint hash */++ struct git_pobject *delta; /* delta base object */+ struct git_pobject *delta_child; /* deltified objects who bases me */+ struct git_pobject *delta_sibling; /* other deltified objects+ * who uses the same base as+ * me */++ void *delta_data;+ unsigned long delta_size;+ unsigned long z_delta_size;++ int written:1,+ recursing:1,+ tagged:1,+ filled:1;+} git_pobject;++struct git_packbuilder {+ git_repository *repo; /* associated repository */+ git_odb *odb; /* associated object database */++ git_hash_ctx ctx;++ uint32_t nr_objects,+ nr_alloc,+ nr_written,+ nr_remaining;++ git_pobject *object_list;++ git_oidmap *object_ix;++ git_oid pack_oid; /* hash of written pack */++ /* synchronization objects */+ git_mutex cache_mutex;+ git_mutex progress_mutex;+ git_cond progress_cond;++ /* configs */+ uint64_t delta_cache_size;+ uint64_t max_delta_cache_size;+ uint64_t cache_max_small_delta_size;+ uint64_t big_file_threshold;+ uint64_t window_memory_limit;++ int nr_threads; /* nr of threads to use */++ bool done;+};++int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);++#endif /* INCLUDE_pack_objects_h__ */
libgit2/src/pack.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -38,7 +38,7 @@ git_oid *found_oid, struct git_pack_file *p, const git_oid *short_oid,- unsigned int len);+ size_t len); static int packfile_error(const char *message) {@@ -46,6 +46,134 @@ return -1; } +/********************+ * Delta base cache+ ********************/++static git_pack_cache_entry *new_cache_object(git_rawobj *source)+{+ git_pack_cache_entry *e = git__calloc(1, sizeof(git_pack_cache_entry));+ if (!e)+ return NULL;++ memcpy(&e->raw, source, sizeof(git_rawobj));++ return e;+}++static void free_cache_object(void *o)+{+ git_pack_cache_entry *e = (git_pack_cache_entry *)o;++ if (e != NULL) {+ assert(e->refcount.val == 0);+ git__free(e->raw.data);+ git__free(e);+ }+}++static void cache_free(git_pack_cache *cache)+{+ khiter_t k;++ if (cache->entries) {+ for (k = kh_begin(cache->entries); k != kh_end(cache->entries); k++) {+ if (kh_exist(cache->entries, k))+ free_cache_object(kh_value(cache->entries, k));+ }++ git_offmap_free(cache->entries);+ git_mutex_free(&cache->lock);+ }+}++static int cache_init(git_pack_cache *cache)+{+ memset(cache, 0, sizeof(git_pack_cache));+ cache->entries = git_offmap_alloc();+ GITERR_CHECK_ALLOC(cache->entries);+ cache->memory_limit = GIT_PACK_CACHE_MEMORY_LIMIT;+ git_mutex_init(&cache->lock);++ return 0;+}++static git_pack_cache_entry *cache_get(git_pack_cache *cache, git_off_t offset)+{+ khiter_t k;+ git_pack_cache_entry *entry = NULL;++ if (git_mutex_lock(&cache->lock) < 0)+ return NULL;++ k = kh_get(off, cache->entries, offset);+ if (k != kh_end(cache->entries)) { /* found it */+ entry = kh_value(cache->entries, k);+ git_atomic_inc(&entry->refcount);+ entry->last_usage = cache->use_ctr++;+ }+ git_mutex_unlock(&cache->lock);++ return entry;+}++/* Run with the cache lock held */+static void free_lowest_entry(git_pack_cache *cache)+{+ git_pack_cache_entry *entry;+ khiter_t k;++ for (k = kh_begin(cache->entries); k != kh_end(cache->entries); k++) {+ if (!kh_exist(cache->entries, k))+ continue;++ entry = kh_value(cache->entries, k);++ if (entry && entry->refcount.val == 0) {+ cache->memory_used -= entry->raw.len;+ kh_del(off, cache->entries, k);+ free_cache_object(entry);+ }+ }+}++static int cache_add(git_pack_cache *cache, git_rawobj *base, git_off_t offset)+{+ git_pack_cache_entry *entry;+ int error, exists = 0;+ khiter_t k;++ if (base->len > GIT_PACK_CACHE_SIZE_LIMIT)+ return -1;++ entry = new_cache_object(base);+ if (entry) {+ if (git_mutex_lock(&cache->lock) < 0) {+ giterr_set(GITERR_OS, "failed to lock cache");+ return -1;+ }+ /* Add it to the cache if nobody else has */+ exists = kh_get(off, cache->entries, offset) != kh_end(cache->entries);+ if (!exists) {+ while (cache->memory_used + base->len > cache->memory_limit)+ free_lowest_entry(cache);++ k = kh_put(off, cache->entries, offset, &error);+ assert(error != 0);+ kh_value(cache->entries, k) = entry;+ cache->memory_used += entry->raw.len;+ }+ git_mutex_unlock(&cache->lock);+ /* Somebody beat us to adding it into the cache */+ if (exists) {+ git__free(entry);+ return -1;+ }+ }++ return 0;+}+ /*********************************************************** * * PACK INDEX METHODS@@ -54,6 +182,10 @@ static void pack_index_free(struct git_pack_file *p) {+ if (p->oids) {+ git__free(p->oids);+ p->oids = NULL;+ } if (p->index_map.data) { git_futils_mmap_free(&p->index_map); p->index_map.data = NULL;@@ -262,7 +394,7 @@ if (base == NULL) return GIT_EBUFS; - ret = packfile_unpack_header1(&used, size_p, type_p, base, left);+ ret = packfile_unpack_header1(&used, size_p, type_p, base, left); git_mwindow_close(w_curs); if (ret == GIT_EBUFS) return ret;@@ -273,6 +405,56 @@ return 0; } +int git_packfile_resolve_header(+ size_t *size_p,+ git_otype *type_p,+ struct git_pack_file *p,+ git_off_t offset)+{+ git_mwindow *w_curs = NULL;+ git_off_t curpos = offset;+ size_t size;+ git_otype type;+ git_off_t base_offset;+ int error;++ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);+ git_mwindow_close(&w_curs);+ if (error < 0)+ return error;++ if (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) {+ size_t base_size;+ git_rawobj delta;+ base_offset = get_delta_base(p, &w_curs, &curpos, type, offset);+ git_mwindow_close(&w_curs);+ error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, size, type);+ git_mwindow_close(&w_curs);+ if (error < 0)+ return error;+ error = git__delta_read_header(delta.data, delta.len, &base_size, size_p);+ git__free(delta.data);+ if (error < 0)+ return error;+ } else+ *size_p = size;++ while (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) {+ curpos = base_offset;+ error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos);+ git_mwindow_close(&w_curs);+ if (error < 0)+ return error;+ if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA)+ break;+ base_offset = get_delta_base(p, &w_curs, &curpos, type, base_offset);+ git_mwindow_close(&w_curs);+ }+ *type_p = type;++ return error;+}+ static int packfile_unpack_delta( git_rawobj *obj, struct git_pack_file *p,@@ -282,9 +464,10 @@ git_otype delta_type, git_off_t obj_offset) {- git_off_t base_offset;+ git_off_t base_offset, base_key; git_rawobj base, delta;- int error;+ git_pack_cache_entry *cached = NULL;+ int error, found_base = 0; base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset); git_mwindow_close(w_curs);@@ -293,32 +476,49 @@ if (base_offset < 0) /* must actually be an error code */ return (int)base_offset; - error = git_packfile_unpack(&base, p, &base_offset);+ if (!p->bases.entries && (cache_init(&p->bases) < 0))+ return -1; - /*- * TODO: git.git tries to load the base from other packfiles- * or loose objects.- *- * We'll need to do this in order to support thin packs.- */- if (error < 0)- return error;+ base_key = base_offset; /* git_packfile_unpack modifies base_offset */+ if ((cached = cache_get(&p->bases, base_offset)) != NULL) {+ memcpy(&base, &cached->raw, sizeof(git_rawobj));+ found_base = 1;+ } + if (!cached) { /* have to inflate it */+ error = git_packfile_unpack(&base, p, &base_offset);++ /*+ * TODO: git.git tries to load the base from other packfiles+ * or loose objects.+ *+ * We'll need to do this in order to support thin packs.+ */+ if (error < 0)+ return error;+ }+ error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type); git_mwindow_close(w_curs);+ if (error < 0) {- git__free(base.data);+ if (!found_base)+ git__free(base.data); return error; } obj->type = base.type; error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len);+ if (error < 0)+ goto on_error; - git__free(base.data);- git__free(delta.data);+ if (found_base)+ git_atomic_dec(&cached->refcount);+ else if (cache_add(&p->bases, &base, base_key) < 0)+ git__free(base.data); - /* TODO: we might want to cache this shit. eventually */- //add_delta_base_cache(p, base_offset, base, base_size, *type);+on_error:+ git__free(delta.data); return error; /* error set by git__delta_apply */ }@@ -387,6 +587,72 @@ git__free(ptr); } +int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos)+{+ int st;++ memset(obj, 0, sizeof(git_packfile_stream));+ obj->curpos = curpos;+ obj->p = p;+ obj->zstream.zalloc = use_git_alloc;+ obj->zstream.zfree = use_git_free;+ obj->zstream.next_in = Z_NULL;+ obj->zstream.next_out = Z_NULL;+ st = inflateInit(&obj->zstream);+ if (st != Z_OK) {+ git__free(obj);+ giterr_set(GITERR_ZLIB, "Failed to inflate packfile");+ return -1;+ }++ return 0;+}++ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len)+{+ unsigned char *in;+ size_t written;+ int st;++ if (obj->done)+ return 0;++ in = pack_window_open(obj->p, &obj->mw, obj->curpos, &obj->zstream.avail_in);+ if (in == NULL)+ return GIT_EBUFS;++ obj->zstream.next_out = buffer;+ obj->zstream.avail_out = (unsigned int)len;+ obj->zstream.next_in = in;++ st = inflate(&obj->zstream, Z_SYNC_FLUSH);+ git_mwindow_close(&obj->mw);++ obj->curpos += obj->zstream.next_in - in;+ written = len - obj->zstream.avail_out;++ if (st != Z_OK && st != Z_STREAM_END) {+ giterr_set(GITERR_ZLIB, "Failed to inflate packfile");+ return -1;+ }++ if (st == Z_STREAM_END)+ obj->done = 1;+++ /* If we didn't write anything out but we're not done, we need more data */+ if (!written && st != Z_STREAM_END)+ return GIT_EBUFS;++ return written;++}++void git_packfile_stream_free(git_packfile_stream *obj)+{+ inflateEnd(&obj->zstream);+}+ int packfile_unpack_compressed( git_rawobj *obj, struct git_pack_file *p,@@ -494,14 +760,14 @@ } else if (type == GIT_OBJ_REF_DELTA) { /* If we have the cooperative cache, search in it first */ if (p->has_cache) {- int pos;- struct git_pack_entry key;+ khiter_t k;+ git_oid oid; - git_oid_fromraw(&key.sha1, base_info);- pos = git_vector_bsearch(&p->cache, &key);- if (pos >= 0) {+ git_oid_fromraw(&oid, base_info);+ k = kh_get(oid, p->idx_cache, &oid);+ if (k != kh_end(p->idx_cache)) { *curpos += 20;- return ((struct git_pack_entry *)git_vector_get(&p->cache, pos))->offset;+ return ((struct git_pack_entry *)kh_value(p->idx_cache, k))->offset; } } /* The base entry _must_ be in the same pack */@@ -529,12 +795,14 @@ } -void packfile_free(struct git_pack_file *p)+void git_packfile_free(struct git_pack_file *p) { assert(p); - /* clear_delta_base_cache(); */+ cache_free(&p->bases);+ git_mwindow_free_all(&p->mwf);+ git_mwindow_file_deregister(&p->mwf); if (p->mwf.fd != -1) p_close(p->mwf.fd);@@ -559,8 +827,10 @@ /* TODO: open with noatime */ p->mwf.fd = git_futils_open_ro(p->pack_name);- if (p->mwf.fd < 0)- return p->mwf.fd;+ if (p->mwf.fd < 0) {+ p->mwf.fd = -1;+ return -1;+ } if (p_fstat(p->mwf.fd, &st) < 0 || git_mwindow_file_register(&p->mwf) < 0)@@ -685,12 +955,76 @@ } } +static int git__memcmp4(const void *a, const void *b) {+ return memcmp(a, b, 4);+}++int git_pack_foreach_entry(+ struct git_pack_file *p,+ git_odb_foreach_cb cb,+ void *data)+{+ const unsigned char *index = p->index_map.data, *current;+ uint32_t i;++ if (index == NULL) {+ int error;++ if ((error = pack_index_open(p)) < 0)+ return error;++ assert(p->index_map.data);++ index = p->index_map.data;+ }++ if (p->index_version > 1) {+ index += 8;+ }++ index += 4 * 256;++ if (p->oids == NULL) {+ git_vector offsets, oids;+ int error;++ if ((error = git_vector_init(&oids, p->num_objects, NULL)))+ return error;++ if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4)))+ return error;++ if (p->index_version > 1) {+ const unsigned char *off = index + 24 * p->num_objects;+ for (i = 0; i < p->num_objects; i++)+ git_vector_insert(&offsets, (void*)&off[4 * i]);+ git_vector_sort(&offsets);+ git_vector_foreach(&offsets, i, current)+ git_vector_insert(&oids, (void*)&index[5 * (current - off)]);+ } else {+ for (i = 0; i < p->num_objects; i++)+ git_vector_insert(&offsets, (void*)&index[24 * i]);+ git_vector_sort(&offsets);+ git_vector_foreach(&offsets, i, current)+ git_vector_insert(&oids, (void*)¤t[4]);+ }+ git_vector_free(&offsets);+ p->oids = (git_oid **)oids.contents;+ }++ for (i = 0; i < p->num_objects; i++)+ if (cb(p->oids[i], data))+ return GIT_EUSER;++ return 0;+}+ static int pack_entry_find_offset( git_off_t *offset_out, git_oid *found_oid, struct git_pack_file *p, const git_oid *short_oid,- unsigned int len)+ size_t len) { const uint32_t *level1_ofs = p->index_map.data; const unsigned char *index = p->index_map.data;@@ -783,7 +1117,7 @@ struct git_pack_entry *e, struct git_pack_file *p, const git_oid *short_oid,- unsigned int len)+ size_t len) { git_off_t offset; git_oid found_oid;
libgit2/src/pack.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,12 +8,15 @@ #ifndef INCLUDE_pack_h__ #define INCLUDE_pack_h__ +#include <zlib.h>+ #include "git2/oid.h" #include "common.h" #include "map.h" #include "mwindow.h" #include "odb.h"+#include "oidmap.h" #define GIT_PACK_FILE_MODE 0444 @@ -51,6 +54,28 @@ uint32_t idx_version; }; +typedef struct git_pack_cache_entry {+ size_t last_usage; /* enough? */+ git_atomic refcount;+ git_rawobj raw;+} git_pack_cache_entry;++#include "offmap.h"++GIT__USE_OFFMAP;+GIT__USE_OIDMAP;++#define GIT_PACK_CACHE_MEMORY_LIMIT 16 * 1024 * 1024+#define GIT_PACK_CACHE_SIZE_LIMIT 1024 * 1024 /* don't bother caching anything over 1MB */++typedef struct {+ size_t memory_used;+ size_t memory_limit;+ size_t use_ctr;+ git_mutex lock;+ git_offmap *entries;+} git_pack_cache;+ struct git_pack_file { git_mwindow_file mwf; git_map index_map;@@ -63,8 +88,11 @@ git_time_t mtime; unsigned pack_local:1, pack_keep:1, has_cache:1; git_oid sha1;- git_vector cache;+ git_oidmap *idx_cache;+ git_oid **oids; + git_pack_cache bases; /* delta base cache */+ /* something like ".git/objects/pack/xxxxx.pack" */ char pack_name[GIT_FLEX_ARRAY]; /* more */ };@@ -75,6 +103,14 @@ struct git_pack_file *p; }; +typedef struct git_packfile_stream {+ git_off_t curpos;+ int done;+ z_stream zstream;+ struct git_pack_file *p;+ git_mwindow *mw;+} git_packfile_stream;+ int git_packfile_unpack_header( size_t *size_p, git_otype *type_p,@@ -82,6 +118,12 @@ git_mwindow **w_curs, git_off_t *curpos); +int git_packfile_resolve_header(+ size_t *size_p,+ git_otype *type_p,+ struct git_pack_file *p,+ git_off_t offset);+ int git_packfile_unpack(git_rawobj *obj, struct git_pack_file *p, git_off_t *obj_offset); int packfile_unpack_compressed( git_rawobj *obj,@@ -91,16 +133,24 @@ size_t size, git_otype type); +int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, git_off_t curpos);+ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t len);+void git_packfile_stream_free(git_packfile_stream *obj);+ git_off_t get_delta_base(struct git_pack_file *p, git_mwindow **w_curs, git_off_t *curpos, git_otype type, git_off_t delta_obj_offset); -void packfile_free(struct git_pack_file *p);+void git_packfile_free(struct git_pack_file *p); int git_packfile_check(struct git_pack_file **pack_out, const char *path); int git_pack_entry_find( struct git_pack_entry *e, struct git_pack_file *p, const git_oid *short_oid,- unsigned int len);+ size_t len);+int git_pack_foreach_entry(+ struct git_pack_file *p,+ git_odb_foreach_cb cb,+ void *data); #endif
libgit2/src/path.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -17,9 +17,55 @@ #include <stdio.h> #include <ctype.h> +#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')++#ifdef GIT_WIN32+static bool looks_like_network_computer_name(const char *path, int pos)+{+ if (pos < 3)+ return false;++ if (path[0] != '/' || path[1] != '/')+ return false;++ while (pos-- > 2) {+ if (path[pos] == '/')+ return false;+ }++ return true;+}+#endif+ /* * Based on the Android implementation, BSD licensed.- * Check http://android.git.kernel.org/+ * http://android.git.kernel.org/+ *+ * Copyright (C) 2008 The Android Open Source Project+ * All rights reserved.+ *+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * * Redistributions of source code must retain the above copyright+ * notice, this list of conditions and the following disclaimer.+ * * Redistributions in binary form must reproduce the above copyright+ * notice, this list of conditions and the following disclaimer in+ * the documentation and/or other materials provided with the+ * distribution.+ *+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+ * AS IS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+ * SUCH DAMAGE. */ int git_path_basename_r(git_buf *buffer, const char *path) {@@ -105,10 +151,19 @@ /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return 'C:/' here */ - if (len == 2 && isalpha(path[0]) && path[1] == ':') {+ if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) { len = 3; goto Exit; }++ /* Similarly checks if we're dealing with a network computer name+ '//computername/.git' will return '//computername/' */++ if (looks_like_network_computer_name(path, len)) {+ len++;+ goto Exit;+ }+ #endif Exit:@@ -145,7 +200,21 @@ return basename; } +size_t git_path_basename_offset(git_buf *buffer)+{+ ssize_t slash; + if (!buffer || buffer->size <= 0)+ return 0;++ slash = git_buf_rfind_next(buffer, '/');++ if (slash >= 0 && buffer->ptr[slash] == '/')+ return (size_t)(slash + 1);++ return 0;+}+ const char *git_path_topdir(const char *path) { size_t len;@@ -168,11 +237,11 @@ { int offset = 0; -#ifdef GIT_WIN32 /* Does the root of the path look like a windows drive ? */- if (isalpha(path[0]) && (path[1] == ':'))+ if (LOOKS_LIKE_DRIVE_PREFIX(path)) offset += 2; +#ifdef GIT_WIN32 /* Are we dealing with a windows network path? */ else if ((path[0] == '/' && path[1] == '/') || (path[0] == '\\' && path[1] == '\\'))@@ -191,6 +260,31 @@ return -1; /* Not a real error - signals that path is not rooted */ } +int git_path_join_unrooted(+ git_buf *path_out, const char *path, const char *base, ssize_t *root_at)+{+ int error, root;++ assert(path && path_out);++ root = git_path_root(path);++ if (base != NULL && root < 0) {+ error = git_buf_joinpath(path_out, base, path);++ if (root_at)+ *root_at = (ssize_t)strlen(base);+ }+ else {+ error = git_buf_sets(path_out, path);++ if (root_at)+ *root_at = (root < 0) ? 0 : (ssize_t)root;+ }++ return error;+}+ int git_path_prettify(git_buf *path_out, const char *path, const char *base) { char buf[GIT_PATH_MAX];@@ -210,7 +304,7 @@ giterr_set(GITERR_OS, "Failed to resolve path '%s'", path); git_buf_clear(path_out);- + return error; } @@ -306,7 +400,7 @@ if (offset >= len || file_url[offset] == '/') return error_invalid_local_file_uri(file_url); -#ifndef _MSC_VER+#ifndef GIT_WIN32 offset--; /* A *nix absolute path starts with a forward slash */ #endif @@ -341,9 +435,10 @@ iter.asize = path->asize; while (scan >= stop) {- if ((error = cb(data, &iter)) < 0)- break;+ error = cb(data, &iter); iter.ptr[scan] = oldc;+ if (error < 0)+ break; scan = git_buf_rfind_next(&iter, '/'); if (scan >= 0) { scan++;@@ -385,6 +480,68 @@ return S_ISREG(st.st_mode) != 0; } +#ifdef GIT_WIN32++bool git_path_is_empty_dir(const char *path)+{+ git_buf pathbuf = GIT_BUF_INIT;+ HANDLE hFind = INVALID_HANDLE_VALUE;+ wchar_t wbuf[GIT_WIN_PATH];+ WIN32_FIND_DATAW ffd;+ bool retval = true;++ if (!git_path_isdir(path)) return false;++ git_buf_printf(&pathbuf, "%s\\*", path);+ git__utf8_to_16(wbuf, GIT_WIN_PATH, git_buf_cstr(&pathbuf));++ hFind = FindFirstFileW(wbuf, &ffd);+ if (INVALID_HANDLE_VALUE == hFind) {+ giterr_set(GITERR_OS, "Couldn't open '%s'", path);+ return false;+ }++ do {+ if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) {+ retval = false;+ }+ } while (FindNextFileW(hFind, &ffd) != 0);++ FindClose(hFind);+ git_buf_free(&pathbuf);+ return retval;+}++#else++bool git_path_is_empty_dir(const char *path)+{+ DIR *dir = NULL;+ struct dirent *e;+ bool retval = true;++ if (!git_path_isdir(path)) return false;++ dir = opendir(path);+ if (!dir) {+ giterr_set(GITERR_OS, "Couldn't open '%s'", path);+ return false;+ }++ while ((e = readdir(dir)) != NULL) {+ if (!git_path_is_dot_or_dotdot(e->d_name)) {+ giterr_set(GITERR_INVALID,+ "'%s' exists and is not an empty directory", path);+ retval = false;+ break;+ }+ }+ closedir(dir);++ return retval;+}+#endif+ int git_path_lstat(const char *path, struct stat *st) { int err = 0;@@ -407,7 +564,7 @@ size_t sub_size = strlen(sub); /* leave base valid even if we could not make space for subdir */- if (git_buf_try_grow(dir, dir_size + sub_size + 2) < 0)+ if (git_buf_try_grow(dir, dir_size + sub_size + 2, false) < 0) return false; /* save excursion */@@ -437,12 +594,7 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base) {- int error;-- if (base != NULL && git_path_root(path) < 0)- error = git_buf_joinpath(dir, base, path);- else- error = git_buf_sets(dir, path);+ int error = git_path_join_unrooted(dir, path, base, NULL); if (!error) { char buf[GIT_PATH_MAX];@@ -460,31 +612,94 @@ return error; } +int git_path_resolve_relative(git_buf *path, size_t ceiling)+{+ char *base, *to, *from, *next;+ size_t len;++ if (!path || git_buf_oom(path))+ return -1;++ if (ceiling > path->size)+ ceiling = path->size;++ /* recognize drive prefixes, etc. that should not be backed over */+ if (ceiling == 0)+ ceiling = git_path_root(path->ptr) + 1;++ /* recognize URL prefixes that should not be backed over */+ if (ceiling == 0) {+ for (next = path->ptr; *next && git__isalpha(*next); ++next);+ if (next[0] == ':' && next[1] == '/' && next[2] == '/')+ ceiling = (next + 3) - path->ptr;+ }++ base = to = from = path->ptr + ceiling;++ while (*from) {+ for (next = from; *next && *next != '/'; ++next);++ len = next - from;++ if (len == 1 && from[0] == '.')+ /* do nothing with singleton dot */;++ else if (len == 2 && from[0] == '.' && from[1] == '.') {+ while (to > base && to[-1] == '/') to--;+ while (to > base && to[-1] != '/') to--;+ }++ else {+ if (*next == '/')+ len++;++ if (to != from)+ memmove(to, from, len);++ to += len;+ }++ from += len;++ while (*from == '/') from++;+ }++ *to = '\0';++ path->size = to - path->ptr;++ return 0;+}++int git_path_apply_relative(git_buf *target, const char *relpath)+{+ git_buf_joinpath(target, git_buf_cstr(target), relpath);+ return git_path_resolve_relative(target, 0);+}+ int git_path_cmp( const char *name1, size_t len1, int isdir1,- const char *name2, size_t len2, int isdir2)+ const char *name2, size_t len2, int isdir2,+ int (*compare)(const char *, const char *, size_t)) {+ unsigned char c1, c2; size_t len = len1 < len2 ? len1 : len2; int cmp; - cmp = memcmp(name1, name2, len);+ cmp = compare(name1, name2, len); if (cmp) return cmp;- if (len1 < len2)- return (!isdir1 && !isdir2) ? -1 :- (isdir1 ? '/' - name2[len1] : name2[len1] - '/');- if (len1 > len2)- return (!isdir1 && !isdir2) ? 1 :- (isdir2 ? name1[len2] - '/' : '/' - name1[len2]);- return 0;-} -/* Taken from git.git */-GIT_INLINE(int) is_dot_or_dotdot(const char *name)-{- return (name[0] == '.' &&- (name[1] == '\0' ||- (name[1] == '.' && name[2] == '\0')));+ c1 = name1[len];+ c2 = name2[len];++ if (c1 == '\0' && isdir1)+ c1 = '/';++ if (c2 == '\0' && isdir2)+ c2 = '/';++ return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } int git_path_direach(@@ -506,7 +721,7 @@ return -1; } -#ifdef __sun+#if defined(__sun) || defined(__GNU__) de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); #else de_buf = git__malloc(sizeof(struct dirent));@@ -515,7 +730,7 @@ while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { int result; - if (is_dot_or_dotdot(de->d_name))+ if (git_path_is_dot_or_dotdot(de->d_name)) continue; if (git_buf_puts(path, de->d_name) < 0) {@@ -560,7 +775,7 @@ return -1; } -#ifdef __sun+#if defined(__sun) || defined(__GNU__) de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); #else de_buf = git__malloc(sizeof(struct dirent));@@ -574,7 +789,7 @@ char *entry_path; size_t entry_len; - if (is_dot_or_dotdot(de->d_name))+ if (git_path_is_dot_or_dotdot(de->d_name)) continue; entry_len = strlen(de->d_name);@@ -609,18 +824,30 @@ int git_path_with_stat_cmp(const void *a, const void *b) { const git_path_with_stat *psa = a, *psb = b;- return git__strcmp_cb(psa->path, psb->path);+ return strcmp(psa->path, psb->path); } +int git_path_with_stat_cmp_icase(const void *a, const void *b)+{+ const git_path_with_stat *psa = a, *psb = b;+ return strcasecmp(psa->path, psb->path);+}+ int git_path_dirload_with_stat( const char *path, size_t prefix_len,+ bool ignore_case,+ const char *start_stat,+ const char *end_stat, git_vector *contents) { int error; unsigned int i; git_path_with_stat *ps; git_buf full = GIT_BUF_INIT;+ int (*strncomp)(const char *a, const char *b, size_t sz);+ size_t start_len = start_stat ? strlen(start_stat) : 0;+ size_t end_len = end_stat ? strlen(end_stat) : 0, cmp_len; if (git_buf_set(&full, path, prefix_len) < 0) return -1;@@ -632,23 +859,45 @@ return error; } + strncomp = ignore_case ? git__strncasecmp : git__strncmp;++ /* stat struct at start of git_path_with_stat, so shift path text */ git_vector_foreach(contents, i, ps) { size_t path_len = strlen((char *)ps);- memmove(ps->path, ps, path_len + 1); ps->path_len = path_len;+ } + git_vector_foreach(contents, i, ps) {+ /* skip if before start_stat or after end_stat */+ cmp_len = min(start_len, ps->path_len);+ if (cmp_len && strncomp(ps->path, start_stat, cmp_len) < 0)+ continue;+ cmp_len = min(end_len, ps->path_len);+ if (cmp_len && strncomp(ps->path, end_stat, cmp_len) > 0)+ continue;++ git_buf_truncate(&full, prefix_len);+ if ((error = git_buf_joinpath(&full, full.ptr, ps->path)) < 0 || (error = git_path_lstat(full.ptr, &ps->st)) < 0) break; - git_buf_truncate(&full, prefix_len);- if (S_ISDIR(ps->st.st_mode)) {- ps->path[path_len] = '/';- ps->path[path_len + 1] = '\0';+ if ((error = git_buf_joinpath(&full, full.ptr, ".git")) < 0)+ break;++ if (p_access(full.ptr, F_OK) == 0) {+ ps->st.st_mode = GIT_FILEMODE_COMMIT;+ } else {+ ps->path[ps->path_len++] = '/';+ ps->path[ps->path_len] = '\0';+ } } }++ /* sort now that directory suffix is added */+ git_vector_sort(contents); git_buf_free(&full);
libgit2/src/path.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -58,6 +58,11 @@ extern char *git_path_basename(const char *path); extern int git_path_basename_r(git_buf *buffer, const char *path); +/* Return the offset of the start of the basename. Unlike the other+ * basename functions, this returns 0 if the path is empty.+ */+extern size_t git_path_basename_offset(git_buf *buffer);+ extern const char *git_path_topdir(const char *path); /**@@ -80,7 +85,24 @@ */ extern void git_path_string_to_dir(char* path, size_t size); +/**+ * Taken from git.git; returns nonzero if the given path is "." or "..".+ */+GIT_INLINE(int) git_path_is_dot_or_dotdot(const char *name)+{+ return (name[0] == '.' &&+ (name[1] == '\0' ||+ (name[1] == '.' && name[2] == '\0')));+}+ #ifdef GIT_WIN32+GIT_INLINE(int) git_path_is_dot_or_dotdotW(const wchar_t *name)+{+ return (name[0] == L'.' &&+ (name[1] == L'\0' ||+ (name[1] == L'.' && name[2] == L'\0')));+}+ /** * Convert backslashes in path to forward slashes. */@@ -130,6 +152,11 @@ extern bool git_path_isfile(const char *path); /**+ * Check if the given path is a directory, and is empty.+ */+extern bool git_path_is_empty_dir(const char *path);++/** * Stat a file and/or link and set error if needed. */ extern int git_path_lstat(const char *path, struct stat *st);@@ -164,6 +191,15 @@ extern bool git_path_contains_file(git_buf *dir, const char *file); /**+ * Prepend base to unrooted path or just copy path over.+ *+ * This will optionally return the index into the path where the "root"+ * is, either the end of the base directory prefix or the path root.+ */+extern int git_path_join_unrooted(+ git_buf *path_out, const char *path, const char *base, ssize_t *root_at);++/** * Clean up path, prepending base if it is not already rooted. */ extern int git_path_prettify(git_buf *path_out, const char *path, const char *base);@@ -186,6 +222,29 @@ extern int git_path_find_dir(git_buf *dir, const char *path, const char *base); /**+ * Resolve relative references within a path.+ *+ * This eliminates "./" and "../" relative references inside a path,+ * as well as condensing multiple slashes into single ones. It will+ * not touch the path before the "ceiling" length.+ *+ * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL+ * prefix and not touch that part of the path.+ */+extern int git_path_resolve_relative(git_buf *path, size_t ceiling);++/**+ * Apply a relative path to base path.+ *+ * Note that the base path could be a filename or a URL and this+ * should still work. The relative path is walked segment by segment+ * with three rules: series of slashes will be condensed to a single+ * slash, "." will be eaten with no change, and ".." will remove a+ * segment from the base path.+ */+extern int git_path_apply_relative(git_buf *target, const char *relpath);++/** * Walk each directory entry, except '.' and '..', calling fn(state). * * @param pathbuf buffer the function reads the initial directory@@ -194,6 +253,7 @@ * the input state and the second arg is pathbuf. The function * may modify the pathbuf, but only by appending new text. * @param state to pass to fn as the first arg.+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ extern int git_path_direach( git_buf *pathbuf,@@ -201,11 +261,12 @@ void *state); /**- * Sort function to order two paths.+ * Sort function to order two paths */ extern int git_path_cmp( const char *name1, size_t len1, int isdir1,- const char *name2, size_t len2, int isdir2);+ const char *name2, size_t len2, int isdir2,+ int (*compare)(const char *, const char *, size_t)); /** * Invoke callback up path directory by directory until the ceiling is@@ -261,18 +322,33 @@ } git_path_with_stat; extern int git_path_with_stat_cmp(const void *a, const void *b);+extern int git_path_with_stat_cmp_icase(const void *a, const void *b); /** * Load all directory entries along with stat info into a vector. *- * This is just like git_path_dirload except that each entry in the- * vector is a git_path_with_stat structure that contains both the- * path and the stat info, plus directories will have a / suffixed- * to their path name.+ * This adds four things on top of plain `git_path_dirload`:+ *+ * 1. Each entry in the vector is a `git_path_with_stat` struct that+ * contains both the path and the stat info+ * 2. The entries will be sorted alphabetically+ * 3. Entries that are directories will be suffixed with a '/'+ * 4. Optionally, you can be a start and end prefix and only elements+ * after the start and before the end (inclusively) will be stat'ed.+ *+ * @param path The directory to read from+ * @param prefix_len The trailing part of path to prefix to entry paths+ * @param ignore_case How to sort and compare paths with start/end limits+ * @param start_stat As optimization, only stat values after this prefix+ * @param end_stat As optimization, only stat values before this prefix+ * @param contents Vector to fill with git_path_with_stat structures */ extern int git_path_dirload_with_stat( const char *path, size_t prefix_len,+ bool ignore_case,+ const char *start_stat,+ const char *end_stat, git_vector *contents); #endif
+ libgit2/src/pathspec.c view
@@ -0,0 +1,168 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "pathspec.h"+#include "buf_text.h"+#include "attr_file.h"++/* what is the common non-wildcard prefix for all items in the pathspec */+char *git_pathspec_prefix(const git_strarray *pathspec)+{+ git_buf prefix = GIT_BUF_INIT;+ const char *scan;++ if (!pathspec || !pathspec->count ||+ git_buf_text_common_prefix(&prefix, pathspec) < 0)+ return NULL;++ /* diff prefix will only be leading non-wildcards */+ for (scan = prefix.ptr; *scan; ++scan) {+ if (git__iswildcard(*scan) &&+ (scan == prefix.ptr || (*(scan - 1) != '\\')))+ break;+ }+ git_buf_truncate(&prefix, scan - prefix.ptr);++ if (prefix.size <= 0) {+ git_buf_free(&prefix);+ return NULL;+ }++ git_buf_text_unescape(&prefix);++ return git_buf_detach(&prefix);+}++/* is there anything in the spec that needs to be filtered on */+bool git_pathspec_is_empty(const git_strarray *pathspec)+{+ size_t i;++ if (pathspec == NULL)+ return true;++ for (i = 0; i < pathspec->count; ++i) {+ const char *str = pathspec->strings[i];++ if (str && str[0])+ return false;+ }++ return true;+}++/* build a vector of fnmatch patterns to evaluate efficiently */+int git_pathspec_init(+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool)+{+ size_t i;++ memset(vspec, 0, sizeof(*vspec));++ if (git_pathspec_is_empty(strspec))+ return 0;++ if (git_vector_init(vspec, strspec->count, NULL) < 0)+ return -1;++ for (i = 0; i < strspec->count; ++i) {+ int ret;+ const char *pattern = strspec->strings[i];+ git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));+ if (!match)+ return -1;++ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;++ ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);+ if (ret == GIT_ENOTFOUND) {+ git__free(match);+ continue;+ } else if (ret < 0)+ return ret;++ if (git_vector_insert(vspec, match) < 0)+ return -1;+ }++ return 0;+}++/* free data from the pathspec vector */+void git_pathspec_free(git_vector *vspec)+{+ git_attr_fnmatch *match;+ unsigned int i;++ git_vector_foreach(vspec, i, match) {+ git__free(match);+ vspec->contents[i] = NULL;+ }++ git_vector_free(vspec);+}++/* match a path against the vectorized pathspec */+bool git_pathspec_match_path(+ git_vector *vspec,+ const char *path,+ bool disable_fnmatch,+ bool casefold,+ const char **matched_pathspec)+{+ size_t i;+ git_attr_fnmatch *match;+ int fnmatch_flags = 0;+ int (*use_strcmp)(const char *, const char *);+ int (*use_strncmp)(const char *, const char *, size_t);++ if (matched_pathspec)+ *matched_pathspec = NULL;++ if (!vspec || !vspec->length)+ return true;++ if (disable_fnmatch)+ fnmatch_flags = -1;+ else if (casefold)+ fnmatch_flags = FNM_CASEFOLD;++ if (casefold) {+ use_strcmp = git__strcasecmp;+ use_strncmp = git__strncasecmp;+ } else {+ use_strcmp = git__strcmp;+ use_strncmp = git__strncmp;+ }++ git_vector_foreach(vspec, i, match) {+ int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH;+ + if (result == FNM_NOMATCH)+ result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0;++ if (fnmatch_flags >= 0 && result == FNM_NOMATCH)+ result = p_fnmatch(match->pattern, path, fnmatch_flags);++ /* if we didn't match, look for exact dirname prefix match */+ if (result == FNM_NOMATCH &&+ (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&+ use_strncmp(path, match->pattern, match->length) == 0 &&+ path[match->length] == '/')+ result = 0;++ if (result == 0) {+ if (matched_pathspec)+ *matched_pathspec = match->pattern;++ return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;+ }+ }++ return false;+}+
+ libgit2/src/pathspec.h view
@@ -0,0 +1,40 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_pathspec_h__+#define INCLUDE_pathspec_h__++#include "common.h"+#include "buffer.h"+#include "vector.h"+#include "pool.h"++/* what is the common non-wildcard prefix for all items in the pathspec */+extern char *git_pathspec_prefix(const git_strarray *pathspec);++/* is there anything in the spec that needs to be filtered on */+extern bool git_pathspec_is_empty(const git_strarray *pathspec);++/* build a vector of fnmatch patterns to evaluate efficiently */+extern int git_pathspec_init(+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool);++/* free data from the pathspec vector */+extern void git_pathspec_free(git_vector *vspec);++/*+ * Match a path against the vectorized pathspec.+ * The matched pathspec is passed back into the `matched_pathspec` parameter,+ * unless it is passed as NULL by the caller.+ */+extern bool git_pathspec_match_path(+ git_vector *vspec,+ const char *path,+ bool disable_fnmatch,+ bool casefold,+ const char **matched_pathspec);++#endif
− libgit2/src/pkt.c
@@ -1,361 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#include "common.h"--#include "git2/types.h"-#include "git2/errors.h"-#include "git2/refs.h"-#include "git2/revwalk.h"--#include "pkt.h"-#include "util.h"-#include "netops.h"-#include "posix.h"-#include "buffer.h"--#include <ctype.h>--#define PKT_LEN_SIZE 4-static const char pkt_done_str[] = "0009done\n";-static const char pkt_flush_str[] = "0000";-static const char pkt_have_prefix[] = "0032have ";-static const char pkt_want_prefix[] = "0032want ";--static int flush_pkt(git_pkt **out)-{- git_pkt *pkt;-- pkt = git__malloc(sizeof(git_pkt));- GITERR_CHECK_ALLOC(pkt);-- pkt->type = GIT_PKT_FLUSH;- *out = pkt;-- return 0;-}--/* the rest of the line will be useful for multi_ack */-static int ack_pkt(git_pkt **out, const char *line, size_t len)-{- git_pkt *pkt;- GIT_UNUSED(line);- GIT_UNUSED(len);-- pkt = git__malloc(sizeof(git_pkt));- GITERR_CHECK_ALLOC(pkt);-- pkt->type = GIT_PKT_ACK;- *out = pkt;-- return 0;-}--static int nak_pkt(git_pkt **out)-{- git_pkt *pkt;-- pkt = git__malloc(sizeof(git_pkt));- GITERR_CHECK_ALLOC(pkt);-- pkt->type = GIT_PKT_NAK;- *out = pkt;-- return 0;-}--static int pack_pkt(git_pkt **out)-{- git_pkt *pkt;-- pkt = git__malloc(sizeof(git_pkt));- GITERR_CHECK_ALLOC(pkt);-- pkt->type = GIT_PKT_PACK;- *out = pkt;-- return 0;-}--static int comment_pkt(git_pkt **out, const char *line, size_t len)-{- git_pkt_comment *pkt;-- pkt = git__malloc(sizeof(git_pkt_comment) + len + 1);- GITERR_CHECK_ALLOC(pkt);-- pkt->type = GIT_PKT_COMMENT;- memcpy(pkt->comment, line, len);- pkt->comment[len] = '\0';-- *out = (git_pkt *) pkt;-- return 0;-}--static int err_pkt(git_pkt **out, const char *line, size_t len)-{- git_pkt_err *pkt;-- /* Remove "ERR " from the line */- line += 4;- len -= 4;- pkt = git__malloc(sizeof(git_pkt_err) + len + 1);- GITERR_CHECK_ALLOC(pkt);-- pkt->type = GIT_PKT_ERR;- memcpy(pkt->error, line, len);- pkt->error[len] = '\0';-- *out = (git_pkt *) pkt;-- return 0;-}--/*- * Parse an other-ref line.- */-static int ref_pkt(git_pkt **out, const char *line, size_t len)-{- int error;- git_pkt_ref *pkt;-- pkt = git__malloc(sizeof(git_pkt_ref));- GITERR_CHECK_ALLOC(pkt);-- memset(pkt, 0x0, sizeof(git_pkt_ref));- pkt->type = GIT_PKT_REF;- if ((error = git_oid_fromstr(&pkt->head.oid, line)) < 0)- goto error_out;-- /* Check for a bit of consistency */- if (line[GIT_OID_HEXSZ] != ' ') {- giterr_set(GITERR_NET, "Error parsing pkt-line");- error = -1;- goto error_out;- }-- /* Jump from the name */- line += GIT_OID_HEXSZ + 1;- len -= (GIT_OID_HEXSZ + 1);-- if (line[len - 1] == '\n')- --len;-- pkt->head.name = git__malloc(len + 1);- GITERR_CHECK_ALLOC(pkt->head.name);-- memcpy(pkt->head.name, line, len);- pkt->head.name[len] = '\0';-- if (strlen(pkt->head.name) < len) {- pkt->capabilities = strchr(pkt->head.name, '\0') + 1;- }-- *out = (git_pkt *)pkt;- return 0;--error_out:- git__free(pkt);- return error;-}--static int32_t parse_len(const char *line)-{- char num[PKT_LEN_SIZE + 1];- int i, error;- int32_t len;- const char *num_end;-- memcpy(num, line, PKT_LEN_SIZE);- num[PKT_LEN_SIZE] = '\0';-- for (i = 0; i < PKT_LEN_SIZE; ++i) {- if (!isxdigit(num[i])) {- giterr_set(GITERR_NET, "Found invalid hex digit in length");- return -1;- }- }-- if ((error = git__strtol32(&len, num, &num_end, 16)) < 0)- return error;-- return len;-}--/*- * As per the documentation, the syntax is:- *- * pkt-line = data-pkt / flush-pkt- * data-pkt = pkt-len pkt-payload- * pkt-len = 4*(HEXDIG)- * pkt-payload = (pkt-len -4)*(OCTET)- * flush-pkt = "0000"- *- * Which means that the first four bytes are the length of the line,- * in ASCII hexadecimal (including itself)- */--int git_pkt_parse_line(- git_pkt **head, const char *line, const char **out, size_t bufflen)-{- int ret;- int32_t len;-- /* Not even enough for the length */- if (bufflen > 0 && bufflen < PKT_LEN_SIZE)- return GIT_EBUFS;-- len = parse_len(line);- if (len < 0) {- /*- * If we fail to parse the length, it might be because the- * server is trying to send us the packfile already.- */- if (bufflen >= 4 && !git__prefixcmp(line, "PACK")) {- giterr_clear();- *out = line;- return pack_pkt(head);- }-- return (int)len;- }-- /*- * If we were given a buffer length, then make sure there is- * enough in the buffer to satisfy this line- */- if (bufflen > 0 && bufflen < (size_t)len)- return GIT_EBUFS;-- line += PKT_LEN_SIZE;- /*- * TODO: How do we deal with empty lines? Try again? with the next- * line?- */- if (len == PKT_LEN_SIZE) {- *out = line;- return 0;- }-- if (len == 0) { /* Flush pkt */- *out = line;- return flush_pkt(head);- }-- len -= PKT_LEN_SIZE; /* the encoded length includes its own size */-- /* Assming the minimal size is actually 4 */- if (!git__prefixcmp(line, "ACK"))- ret = ack_pkt(head, line, len);- else if (!git__prefixcmp(line, "NAK"))- ret = nak_pkt(head);- else if (!git__prefixcmp(line, "ERR "))- ret = err_pkt(head, line, len);- else if (*line == '#')- ret = comment_pkt(head, line, len);- else- ret = ref_pkt(head, line, len);-- *out = line + len;-- return ret;-}--void git_pkt_free(git_pkt *pkt)-{- if (pkt->type == GIT_PKT_REF) {- git_pkt_ref *p = (git_pkt_ref *) pkt;- git__free(p->head.name);- }-- git__free(pkt);-}--int git_pkt_buffer_flush(git_buf *buf)-{- return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));-}--int git_pkt_send_flush(GIT_SOCKET s)-{-- return gitno_send(s, pkt_flush_str, strlen(pkt_flush_str), 0);-}--static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf)-{- char capstr[20];- char oid[GIT_OID_HEXSZ +1] = {0};- unsigned int len;-- if (caps->ofs_delta)- strncpy(capstr, GIT_CAP_OFS_DELTA, sizeof(capstr));-- len = (unsigned int)- (strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ +- strlen(capstr) + 1 /* LF */);- git_buf_grow(buf, git_buf_len(buf) + len);-- git_oid_fmt(oid, &head->oid);- return git_buf_printf(buf, "%04xwant %s%c%s\n", len, oid, 0, capstr);-}--/*- * All "want" packets have the same length and format, so what we do- * is overwrite the OID each time.- */--int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf)-{- unsigned int i = 0;- git_remote_head *head;-- if (caps->common) {- for (; i < refs->length; ++i) {- head = refs->contents[i];- if (!head->local)- break;- }-- if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0)- return -1;-- i++;- }-- for (; i < refs->length; ++i) {- char oid[GIT_OID_HEXSZ];-- head = refs->contents[i];- if (head->local)- continue;-- git_oid_fmt(oid, &head->oid);- git_buf_put(buf, pkt_want_prefix, strlen(pkt_want_prefix));- git_buf_put(buf, oid, GIT_OID_HEXSZ);- git_buf_putc(buf, '\n');- if (git_buf_oom(buf))- return -1;- }-- return git_pkt_buffer_flush(buf);-}--int git_pkt_buffer_have(git_oid *oid, git_buf *buf)-{- char oidhex[GIT_OID_HEXSZ + 1];-- memset(oidhex, 0x0, sizeof(oidhex));- git_oid_fmt(oidhex, oid);- return git_buf_printf(buf, "%s%s\n", pkt_have_prefix, oidhex);-}--int git_pkt_buffer_done(git_buf *buf)-{- return git_buf_puts(buf, pkt_done_str);-}
− libgit2/src/pkt.h
@@ -1,81 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#ifndef INCLUDE_pkt_h__-#define INCLUDE_pkt_h__--#include "common.h"-#include "transport.h"-#include "buffer.h"-#include "posix.h"-#include "git2/net.h"--enum git_pkt_type {- GIT_PKT_CMD,- GIT_PKT_FLUSH,- GIT_PKT_REF,- GIT_PKT_HAVE,- GIT_PKT_ACK,- GIT_PKT_NAK,- GIT_PKT_PACK,- GIT_PKT_COMMENT,- GIT_PKT_ERR,-};--/* Used for multi-ack */-enum git_ack_status {- GIT_ACK_NONE,- GIT_ACK_CONTINUE,- GIT_ACK_COMMON,- GIT_ACK_READY-};--/* This would be a flush pkt */-typedef struct {- enum git_pkt_type type;-} git_pkt;--struct git_pkt_cmd {- enum git_pkt_type type;- char *cmd;- char *path;- char *host;-};--/* This is a pkt-line with some info in it */-typedef struct {- enum git_pkt_type type;- git_remote_head head;- char *capabilities;-} git_pkt_ref;--/* Useful later */-typedef struct {- enum git_pkt_type type;- git_oid oid;- enum git_ack_status status;-} git_pkt_ack;--typedef struct {- enum git_pkt_type type;- char comment[GIT_FLEX_ARRAY];-} git_pkt_comment;--typedef struct {- enum git_pkt_type type;- char error[GIT_FLEX_ARRAY];-} git_pkt_err;--int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);-int git_pkt_buffer_flush(git_buf *buf);-int git_pkt_send_flush(GIT_SOCKET s);-int git_pkt_buffer_done(git_buf *buf);-int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf);-int git_pkt_buffer_have(git_oid *oid, git_buf *buf);-void git_pkt_free(git_pkt *pkt);--#endif
libgit2/src/pool.c view
@@ -10,6 +10,10 @@ char data[GIT_FLEX_ARRAY]; }; +struct pool_freelist {+ struct pool_freelist *next;+};+ #define GIT_POOL_MIN_USABLE 4 #define GIT_POOL_MIN_PAGESZ 2 * sizeof(void*) @@ -150,7 +154,7 @@ pool->has_multi_item_alloc = 1; else if (pool->free_list != NULL) { ptr = pool->free_list;- pool->free_list = *((void **)pool->free_list);+ pool->free_list = ((struct pool_freelist *)pool->free_list)->next; return ptr; } @@ -206,6 +210,11 @@ return git_pool_strndup(pool, str, strlen(str)); } +char *git_pool_strdup_safe(git_pool *pool, const char *str)+{+ return str ? git_pool_strdup(pool, str) : NULL;+}+ char *git_pool_strcat(git_pool *pool, const char *a, const char *b) { void *ptr;@@ -230,12 +239,33 @@ void git_pool_free(git_pool *pool, void *ptr) {- assert(pool && ptr && pool->item_size >= sizeof(void*));+ struct pool_freelist *item = ptr; - *((void **)ptr) = pool->free_list;- pool->free_list = ptr;+ assert(pool && pool->item_size >= sizeof(void*));++ if (item) {+ item->next = pool->free_list;+ pool->free_list = item;+ } } +void git_pool_free_array(git_pool *pool, size_t count, void **ptrs)+{+ struct pool_freelist **items = (struct pool_freelist **)ptrs;+ size_t i;++ assert(pool && ptrs && pool->item_size >= sizeof(void*));++ if (!count)+ return;++ for (i = count - 1; i > 0; --i)+ items[i]->next = items[i - 1];++ items[i]->next = pool->free_list;+ pool->free_list = items[count - 1];+}+ uint32_t git_pool__open_pages(git_pool *pool) { uint32_t ct = 0;@@ -275,6 +305,8 @@ SYSTEM_INFO info; GetSystemInfo(&info); size = (uint32_t)info.dwPageSize;+#elif defined(__amigaos4__)+ size = (uint32_t)4096; /* 4K as there is no global value we can query */ #else size = (uint32_t)sysconf(_SC_PAGE_SIZE); #endif
libgit2/src/pool.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -76,6 +76,17 @@ extern void *git_pool_malloc(git_pool *pool, uint32_t items); /**+ * Allocate space and zero it out.+ */+GIT_INLINE(void *) git_pool_mallocz(git_pool *pool, uint32_t items)+{+ void *ptr = git_pool_malloc(pool, items);+ if (ptr)+ memset(ptr, 0, (size_t)items * (size_t)pool->item_size);+ return ptr;+}++/** * Allocate space and duplicate string data into it. * * This is allowed only for pools with item_size == sizeof(char)@@ -90,6 +101,13 @@ extern char *git_pool_strdup(git_pool *pool, const char *str); /**+ * Allocate space and duplicate a string into it, NULL is no error.+ *+ * This is allowed only for pools with item_size == sizeof(char)+ */+extern char *git_pool_strdup_safe(git_pool *pool, const char *str);++/** * Allocate space for the concatenation of two strings. * * This is allowed only for pools with item_size == sizeof(char)@@ -107,6 +125,13 @@ * native block size is large enough to hold a void pointer */ extern void git_pool_free(git_pool *pool, void *ptr);++/**+ * Push an array of pool allocated blocks efficiently onto the free list.+ *+ * This has the same constraints as `git_pool_free()` above.+ */+extern void git_pool_free_array(git_pool *pool, size_t count, void **ptrs); /* * Misc utilities
libgit2/src/posix.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -12,12 +12,98 @@ #ifndef GIT_WIN32 +#ifdef NO_ADDRINFO++int p_getaddrinfo(+ const char *host,+ const char *port,+ struct addrinfo *hints,+ struct addrinfo **info)+{+ struct addrinfo *ainfo, *ai;+ int p = 0;++ GIT_UNUSED(hints);++ if ((ainfo = malloc(sizeof(struct addrinfo))) == NULL)+ return -1;++ if ((ainfo->ai_hostent = gethostbyname(host)) == NULL) {+ free(ainfo);+ return -2;+ }++ ainfo->ai_servent = getservbyname(port, 0);++ if (ainfo->ai_servent)+ ainfo->ai_port = ainfo->ai_servent->s_port;+ else+ ainfo->ai_port = atol(port);++ memcpy(&ainfo->ai_addr_in.sin_addr,+ ainfo->ai_hostent->h_addr_list[0],+ ainfo->ai_hostent->h_length);++ ainfo->ai_protocol = 0;+ ainfo->ai_socktype = hints->ai_socktype;+ ainfo->ai_family = ainfo->ai_hostent->h_addrtype;+ ainfo->ai_addr_in.sin_family = ainfo->ai_family;+ ainfo->ai_addr_in.sin_port = ainfo->ai_port;+ ainfo->ai_addr = (struct addrinfo *)&ainfo->ai_addr_in;+ ainfo->ai_addrlen = sizeof(struct sockaddr_in);++ *info = ainfo;++ if (ainfo->ai_hostent->h_addr_list[1] == NULL) {+ ainfo->ai_next = NULL;+ return 0;+ }++ ai = ainfo;++ for (p = 1; ainfo->ai_hostent->h_addr_list[p] != NULL; p++) {+ ai->ai_next = malloc(sizeof(struct addrinfo));+ memcpy(&ai->ai_next, ainfo, sizeof(struct addrinfo));+ memcpy(&ai->ai_next->ai_addr_in.sin_addr,+ ainfo->ai_hostent->h_addr_list[p],+ ainfo->ai_hostent->h_length);+ ai->ai_next->ai_addr = (struct addrinfo *)&ai->ai_next->ai_addr_in;+ ai = ai->ai_next;+ }++ ai->ai_next = NULL;+ return 0;+}++void p_freeaddrinfo(struct addrinfo *info)+{+ struct addrinfo *p, *next;++ p = info;++ while(p != NULL) {+ next = p->ai_next;+ free(p);+ p = next;+ }+}++const char *p_gai_strerror(int ret)+{+ switch(ret) {+ case -1: return "Out of memory"; break;+ case -2: return "Address lookup failed"; break;+ default: return "Unknown error"; break;+ }+}++#endif /* NO_ADDRINFO */+ int p_open(const char *path, int flags, ...) { mode_t mode = 0; - if (flags & O_CREAT)- {+ if (flags & O_CREAT) { va_list arg_list; va_start(arg_list, flags);@@ -63,11 +149,12 @@ return -1; } -#endif+#endif /* GIT_WIN32 */ int p_read(git_file fd, void *buf, size_t cnt) { char *b = buf;+ while (cnt) { ssize_t r; #ifdef GIT_WIN32@@ -92,6 +179,7 @@ int p_write(git_file fd, const void *buf, size_t cnt) { const char *b = buf;+ while (cnt) { ssize_t r; #ifdef GIT_WIN32@@ -114,3 +202,5 @@ } return 0; }++
libgit2/src/posix.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,10 +10,18 @@ #include "common.h" #include <fcntl.h> #include <time.h>+#include "fnmatch.h" +#ifndef S_IFGITLINK #define S_IFGITLINK 0160000 #define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)+#endif +/* if S_ISGID is not defined, then don't try to set it */+#ifndef S_ISGID+#define S_ISGID 0+#endif+ #if !defined(O_BINARY) #define O_BINARY 0 #endif@@ -24,14 +32,13 @@ * Standard POSIX Methods * * All the methods starting with the `p_` prefix are- * direct ports of the standard POSIX methods. + * direct ports of the standard POSIX methods. * * Some of the methods are slightly wrapped to provide * saner defaults. Some of these methods are emulated * in Windows platforns. * * Use your manpages to check the docs on these.- * Straightforward */ extern int p_read(git_file fd, void *buf, size_t cnt);@@ -59,10 +66,19 @@ typedef int GIT_SOCKET; #define INVALID_SOCKET -1 +#define p_localtime_r localtime_r+#define p_gmtime_r gmtime_r+#define p_gettimeofday gettimeofday+ #else typedef SOCKET GIT_SOCKET;+struct timezone;+extern struct tm * p_localtime_r (const time_t *timer, struct tm *result);+extern struct tm * p_gmtime_r (const time_t *timer, struct tm *result);+extern int p_gettimeofday(struct timeval *tv, struct timezone *tz); + #endif /**@@ -74,6 +90,41 @@ # include "unix/posix.h" #endif -#define p_readdir_r(d,e,r) readdir_r(d,e,r)+#ifdef NO_READDIR_R+# include <dirent.h>+GIT_INLINE(int) p_readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)+{+ GIT_UNUSED(entry);+ *result = readdir(dirp);+ return 0;+}+#else /* NO_READDIR_R */+# define p_readdir_r(d,e,r) readdir_r(d,e,r)+#endif++#ifdef NO_ADDRINFO+# include <netdb.h>+struct addrinfo {+ struct hostent *ai_hostent;+ struct servent *ai_servent;+ struct sockaddr_in ai_addr_in;+ struct sockaddr *ai_addr;+ size_t ai_addrlen;+ int ai_family;+ int ai_socktype;+ int ai_protocol;+ long ai_port;+ struct addrinfo *ai_next;+};++extern int p_getaddrinfo(const char *host, const char *port,+ struct addrinfo *hints, struct addrinfo **info);+extern void p_freeaddrinfo(struct addrinfo *info);+extern const char *p_gai_strerror(int ret);+#else+# define p_getaddrinfo(a, b, c, d) getaddrinfo(a, b, c, d)+# define p_freeaddrinfo(a) freeaddrinfo(a)+# define p_gai_strerror(c) gai_strerror(c)+#endif /* NO_ADDRINFO */ #endif
libgit2/src/pqueue.c view
@@ -1,8 +1,30 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.+ *+ * This file is based on a modified version of the priority queue found+ * in the Apache project and libpqueue library.+ * + * https://github.com/vy/libpqueue+ * + * Original file notice:+ * + * Copyright 2010 Volkan Yazici <volkan.yazici@gmail.com>+ * Copyright 2006-2010 The Apache Software Foundation+ *+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not+ * use this file except in compliance with the License. You may obtain a copy of+ * the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the+ * License for the specific language governing permissions and limitations under+ * the License. */ #include "common.h"
libgit2/src/pqueue.h view
@@ -1,8 +1,30 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.+ * + * This file is based on a modified version of the priority queue found+ * in the Apache project and libpqueue library.+ * + * https://github.com/vy/libpqueue+ * + * Original file notice:+ * + * Copyright 2010 Volkan Yazici <volkan.yazici@gmail.com>+ * Copyright 2006-2010 The Apache Software Foundation+ *+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not+ * use this file except in compliance with the License. You may obtain a copy of+ * the License at+ *+ * http://www.apache.org/licenses/LICENSE-2.0+ *+ * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the+ * License for the specific language governing permissions and limitations under+ * the License. */ #ifndef INCLUDE_pqueue_h__
− libgit2/src/protocol.c
@@ -1,58 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */-#include "common.h"-#include "protocol.h"-#include "pkt.h"-#include "buffer.h"--int git_protocol_store_refs(git_protocol *p, const char *data, size_t len)-{- git_buf *buf = &p->buf;- git_vector *refs = p->refs;- int error;- const char *line_end, *ptr;-- if (len == 0) { /* EOF */- if (git_buf_len(buf) != 0) {- giterr_set(GITERR_NET, "Unexpected EOF");- return p->error = -1;- } else {- return 0;- }- }-- git_buf_put(buf, data, len);- ptr = buf->ptr;- while (1) {- git_pkt *pkt;-- if (git_buf_len(buf) == 0)- return 0;-- error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf));- if (error == GIT_EBUFS)- return 0; /* Ask for more */- if (error < 0)- return p->error = -1;-- git_buf_consume(buf, line_end);-- if (pkt->type == GIT_PKT_ERR) {- giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);- git__free(pkt);- return -1;- }-- if (git_vector_insert(refs, pkt) < 0)- return p->error = -1;-- if (pkt->type == GIT_PKT_FLUSH)- p->flush = 1;- }-- return 0;-}
− libgit2/src/protocol.h
@@ -1,23 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */-#ifndef INCLUDE_protocol_h__-#define INCLUDE_protocol_h__--#include "transport.h"-#include "buffer.h"--typedef struct {- git_transport *transport;- git_vector *refs;- git_buf buf;- int error;- unsigned int flush :1;-} git_protocol;--int git_protocol_store_refs(git_protocol *p, const char *data, size_t len);--#endif
+ libgit2/src/push.c view
@@ -0,0 +1,653 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "git2.h"++#include "common.h"+#include "pack.h"+#include "pack-objects.h"+#include "remote.h"+#include "vector.h"+#include "push.h"+#include "tree.h"++static int push_spec_rref_cmp(const void *a, const void *b)+{+ const push_spec *push_spec_a = a, *push_spec_b = b;++ return strcmp(push_spec_a->rref, push_spec_b->rref);+}++static int push_status_ref_cmp(const void *a, const void *b)+{+ const push_status *push_status_a = a, *push_status_b = b;++ return strcmp(push_status_a->ref, push_status_b->ref);+}++int git_push_new(git_push **out, git_remote *remote)+{+ git_push *p;++ *out = NULL;++ p = git__calloc(1, sizeof(*p));+ GITERR_CHECK_ALLOC(p);++ p->repo = remote->repo;+ p->remote = remote;+ p->report_status = 1;+ p->pb_parallelism = 1;++ if (git_vector_init(&p->specs, 0, push_spec_rref_cmp) < 0) {+ git__free(p);+ return -1;+ }++ if (git_vector_init(&p->status, 0, push_status_ref_cmp) < 0) {+ git_vector_free(&p->specs);+ git__free(p);+ return -1;+ }++ *out = p;+ return 0;+}++int git_push_set_options(git_push *push, const git_push_options *opts)+{+ if (!push || !opts)+ return -1;++ GITERR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options");++ push->pb_parallelism = opts->pb_parallelism;++ return 0;+}++static void free_refspec(push_spec *spec)+{+ if (spec == NULL)+ return;++ if (spec->lref)+ git__free(spec->lref);++ if (spec->rref)+ git__free(spec->rref);++ git__free(spec);+}++static int check_rref(char *ref)+{+ if (git__prefixcmp(ref, "refs/")) {+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);+ return -1;+ }++ return 0;+}++static int check_lref(git_push *push, char *ref)+{+ /* lref must be resolvable to an existing object */+ git_object *obj;+ int error = git_revparse_single(&obj, push->repo, ref);+ git_object_free(obj);++ if (!error)+ return 0;++ if (error == GIT_ENOTFOUND)+ giterr_set(GITERR_REFERENCE,+ "src refspec '%s' does not match any existing object", ref);+ else+ giterr_set(GITERR_INVALID, "Not a valid reference '%s'", ref);+ return -1;+}++static int parse_refspec(git_push *push, push_spec **spec, const char *str)+{+ push_spec *s;+ char *delim;++ *spec = NULL;++ s = git__calloc(1, sizeof(*s));+ GITERR_CHECK_ALLOC(s);++ if (str[0] == '+') {+ s->force = true;+ str++;+ }++ delim = strchr(str, ':');+ if (delim == NULL) {+ s->lref = git__strdup(str);+ if (!s->lref || check_lref(push, s->lref) < 0)+ goto on_error;+ } else {+ if (delim - str) {+ s->lref = git__strndup(str, delim - str);+ if (!s->lref || check_lref(push, s->lref) < 0)+ goto on_error;+ }++ if (strlen(delim + 1)) {+ s->rref = git__strdup(delim + 1);+ if (!s->rref || check_rref(s->rref) < 0)+ goto on_error;+ }+ }++ if (!s->lref && !s->rref)+ goto on_error;++ /* If rref is ommitted, use the same ref name as lref */+ if (!s->rref) {+ s->rref = git__strdup(s->lref);+ if (!s->rref || check_rref(s->rref) < 0)+ goto on_error;+ }++ *spec = s;+ return 0;++on_error:+ free_refspec(s);+ return -1;+}++int git_push_add_refspec(git_push *push, const char *refspec)+{+ push_spec *spec;++ if (parse_refspec(push, &spec, refspec) < 0 ||+ git_vector_insert(&push->specs, spec) < 0)+ return -1;++ return 0;+}++int git_push_update_tips(git_push *push)+{+ git_refspec *fetch_spec = &push->remote->fetch;+ git_buf remote_ref_name = GIT_BUF_INIT;+ size_t i, j;+ push_spec *push_spec;+ git_reference *remote_ref;+ push_status *status;+ int error = 0;++ git_vector_foreach(&push->status, i, status) {+ /* If this ref update was successful (ok, not ng), it will have an empty message */+ if (status->msg)+ continue;++ /* Find the corresponding remote ref */+ if (!git_refspec_src_matches(fetch_spec, status->ref))+ continue;++ if ((error = git_refspec_transform_r(&remote_ref_name, fetch_spec, status->ref)) < 0)+ goto on_error;++ /* Find matching push ref spec */+ git_vector_foreach(&push->specs, j, push_spec) {+ if (!strcmp(push_spec->rref, status->ref))+ break;+ }++ /* Could not find the corresponding push ref spec for this push update */+ if (j == push->specs.length)+ continue;++ /* Update the remote ref */+ if (git_oid_iszero(&push_spec->loid)) {+ error = git_reference_lookup(&remote_ref, push->remote->repo, git_buf_cstr(&remote_ref_name));++ if (!error) {+ if ((error = git_reference_delete(remote_ref)) < 0) {+ git_reference_free(remote_ref);+ goto on_error;+ }+ git_reference_free(remote_ref);+ } else if (error == GIT_ENOTFOUND)+ giterr_clear();+ else+ goto on_error;+ } else if ((error = git_reference_create(NULL, push->remote->repo, git_buf_cstr(&remote_ref_name), &push_spec->loid, 1)) < 0)+ goto on_error;+ }++ error = 0;++on_error:+ git_buf_free(&remote_ref_name);+ return error;+}++static int revwalk(git_vector *commits, git_push *push)+{+ git_remote_head *head;+ push_spec *spec;+ git_revwalk *rw;+ git_oid oid;+ unsigned int i;+ int error = -1;++ if (git_revwalk_new(&rw, push->repo) < 0)+ return -1;++ git_revwalk_sorting(rw, GIT_SORT_TIME);++ git_vector_foreach(&push->specs, i, spec) {+ git_otype type;+ size_t size;++ if (git_oid_iszero(&spec->loid))+ /*+ * Delete reference on remote side;+ * nothing to do here.+ */+ continue;++ if (git_oid_equal(&spec->loid, &spec->roid))+ continue; /* up-to-date */++ if (git_odb_read_header(&size, &type, push->repo->_odb, &spec->loid) < 0)+ goto on_error;++ if (type == GIT_OBJ_TAG) {+ git_tag *tag;+ git_object *target;++ if (git_packbuilder_insert(push->pb, &spec->loid, NULL) < 0)+ goto on_error;++ if (git_tag_lookup(&tag, push->repo, &spec->loid) < 0)+ goto on_error;++ if (git_tag_peel(&target, tag) < 0) {+ git_tag_free(tag);+ goto on_error;+ }+ git_tag_free(tag);++ if (git_object_type(target) == GIT_OBJ_COMMIT) {+ if (git_revwalk_push(rw, git_object_id(target)) < 0) {+ git_object_free(target);+ goto on_error;+ }+ } else {+ if (git_packbuilder_insert(+ push->pb, git_object_id(target), NULL) < 0) {+ git_object_free(target);+ goto on_error;+ }+ }+ git_object_free(target);+ } else if (git_revwalk_push(rw, &spec->loid) < 0)+ goto on_error;++ if (!spec->force) {+ git_oid base;++ if (git_oid_iszero(&spec->roid))+ continue;++ if (!git_odb_exists(push->repo->_odb, &spec->roid)) {+ giterr_clear();+ error = GIT_ENONFASTFORWARD;+ goto on_error;+ }++ error = git_merge_base(&base, push->repo,+ &spec->loid, &spec->roid);++ if (error == GIT_ENOTFOUND ||+ (!error && !git_oid_equal(&base, &spec->roid))) {+ giterr_clear();+ error = GIT_ENONFASTFORWARD;+ goto on_error;+ }++ if (error < 0)+ goto on_error;+ }+ }++ git_vector_foreach(&push->remote->refs, i, head) {+ if (git_oid_iszero(&head->oid))+ continue;++ /* TODO */+ git_revwalk_hide(rw, &head->oid);+ }++ while ((error = git_revwalk_next(&oid, rw)) == 0) {+ git_oid *o = git__malloc(GIT_OID_RAWSZ);+ GITERR_CHECK_ALLOC(o);+ git_oid_cpy(o, &oid);+ if (git_vector_insert(commits, o) < 0) {+ error = -1;+ goto on_error;+ }+ }++on_error:+ git_revwalk_free(rw);+ return error == GIT_ITEROVER ? 0 : error;+}++static int enqueue_object(+ const git_tree_entry *entry,+ git_packbuilder *pb)+{+ switch (git_tree_entry_type(entry)) {+ case GIT_OBJ_COMMIT:+ return 0;+ case GIT_OBJ_TREE:+ return git_packbuilder_insert_tree(pb, &entry->oid);+ default:+ return git_packbuilder_insert(pb, &entry->oid, entry->filename);+ }+}++static int queue_differences(+ git_tree *base,+ git_tree *delta,+ git_packbuilder *pb)+{+ git_tree *b_child = NULL, *d_child = NULL;+ size_t b_length = git_tree_entrycount(base);+ size_t d_length = git_tree_entrycount(delta);+ size_t i = 0, j = 0;+ int error;++ while (i < b_length && j < d_length) {+ const git_tree_entry *b_entry = git_tree_entry_byindex(base, i);+ const git_tree_entry *d_entry = git_tree_entry_byindex(delta, j);+ int cmp = 0;++ if (!git_oid_cmp(&b_entry->oid, &d_entry->oid))+ goto loop;++ cmp = strcmp(b_entry->filename, d_entry->filename);++ /* If the entries are both trees and they have the same name but are+ * different, then we'll recurse after adding the right-hand entry */+ if (!cmp &&+ git_tree_entry__is_tree(b_entry) &&+ git_tree_entry__is_tree(d_entry)) {+ /* Add the right-hand entry */+ if ((error = git_packbuilder_insert(pb, &d_entry->oid,+ d_entry->filename)) < 0)+ goto on_error;++ /* Acquire the subtrees and recurse */+ if ((error = git_tree_lookup(&b_child,+ git_tree_owner(base), &b_entry->oid)) < 0 ||+ (error = git_tree_lookup(&d_child,+ git_tree_owner(delta), &d_entry->oid)) < 0 ||+ (error = queue_differences(b_child, d_child, pb)) < 0)+ goto on_error;++ git_tree_free(b_child); b_child = NULL;+ git_tree_free(d_child); d_child = NULL;+ }+ /* If the object is new or different in the right-hand tree,+ * then enumerate it */+ else if (cmp >= 0 &&+ (error = enqueue_object(d_entry, pb)) < 0)+ goto on_error;++ loop:+ if (cmp <= 0) i++;+ if (cmp >= 0) j++;+ }++ /* Drain the right-hand tree of entries */+ for (; j < d_length; j++)+ if ((error = enqueue_object(git_tree_entry_byindex(delta, j), pb)) < 0)+ goto on_error;++ error = 0;++on_error:+ if (b_child)+ git_tree_free(b_child);++ if (d_child)+ git_tree_free(d_child);++ return error;+}++static int queue_objects(git_push *push)+{+ git_vector commits = GIT_VECTOR_INIT;+ git_oid *oid;+ size_t i;+ unsigned j;+ int error;++ if ((error = revwalk(&commits, push)) < 0)+ goto on_error;++ git_vector_foreach(&commits, i, oid) {+ git_commit *parent = NULL, *commit;+ git_tree *tree = NULL, *ptree = NULL;+ size_t parentcount;++ if ((error = git_commit_lookup(&commit, push->repo, oid)) < 0)+ goto on_error;++ /* Insert the commit */+ if ((error = git_packbuilder_insert(push->pb, oid, NULL)) < 0)+ goto loop_error;++ parentcount = git_commit_parentcount(commit);++ if (!parentcount) {+ if ((error = git_packbuilder_insert_tree(push->pb,+ git_commit_tree_id(commit))) < 0)+ goto loop_error;+ } else {+ if ((error = git_tree_lookup(&tree, push->repo,+ git_commit_tree_id(commit))) < 0 ||+ (error = git_packbuilder_insert(push->pb,+ git_commit_tree_id(commit), NULL)) < 0)+ goto loop_error;++ /* For each parent, add the items which are different */+ for (j = 0; j < parentcount; j++) {+ if ((error = git_commit_parent(&parent, commit, j)) < 0 ||+ (error = git_commit_tree(&ptree, parent)) < 0 ||+ (error = queue_differences(ptree, tree, push->pb)) < 0)+ goto loop_error;++ git_tree_free(ptree); ptree = NULL;+ git_commit_free(parent); parent = NULL;+ }+ }++ error = 0;++ loop_error:+ if (tree)+ git_tree_free(tree);++ if (ptree)+ git_tree_free(ptree);++ if (parent)+ git_commit_free(parent);++ git_commit_free(commit);++ if (error < 0)+ goto on_error;+ }++ error = 0;++on_error:+ git_vector_foreach(&commits, i, oid)+ git__free(oid);++ git_vector_free(&commits);+ return error;+}++static int calculate_work(git_push *push)+{+ git_remote_head *head;+ push_spec *spec;+ unsigned int i, j;++ /* Update local and remote oids*/++ git_vector_foreach(&push->specs, i, spec) {+ if (spec->lref) {+ /* This is a create or update. Local ref must exist. */+ if (git_reference_name_to_id(+ &spec->loid, push->repo, spec->lref) < 0) {+ giterr_set(GIT_ENOTFOUND, "No such reference '%s'", spec->lref);+ return -1;+ }+ }++ if (spec->rref) {+ /* Remote ref may or may not (e.g. during create) already exist. */+ git_vector_foreach(&push->remote->refs, j, head) {+ if (!strcmp(spec->rref, head->name)) {+ git_oid_cpy(&spec->roid, &head->oid);+ break;+ }+ }+ }+ }++ return 0;+}++static int do_push(git_push *push)+{+ int error;+ git_transport *transport = push->remote->transport;++ if (!transport->push) {+ giterr_set(GITERR_NET, "Remote transport doesn't support push");+ error = -1;+ goto on_error;+ }++ /*+ * A pack-file MUST be sent if either create or update command+ * is used, even if the server already has all the necessary+ * objects. In this case the client MUST send an empty pack-file.+ */++ if ((error = git_packbuilder_new(&push->pb, push->repo)) < 0)+ goto on_error;++ git_packbuilder_set_threads(push->pb, push->pb_parallelism);++ if ((error = calculate_work(push)) < 0 ||+ (error = queue_objects(push)) < 0 ||+ (error = transport->push(transport, push)) < 0)+ goto on_error;++ error = 0;++on_error:+ git_packbuilder_free(push->pb);+ return error;+}++static int cb_filter_refs(git_remote_head *ref, void *data)+{+ git_remote *remote = (git_remote *) data;+ return git_vector_insert(&remote->refs, ref);+}++static int filter_refs(git_remote *remote)+{+ git_vector_clear(&remote->refs);+ return git_remote_ls(remote, cb_filter_refs, remote);+}++int git_push_finish(git_push *push)+{+ int error;++ if (!git_remote_connected(push->remote) &&+ (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH)) < 0)+ return error;++ if ((error = filter_refs(push->remote)) < 0 ||+ (error = do_push(push)) < 0)+ return error;++ return 0;+}++int git_push_unpack_ok(git_push *push)+{+ return push->unpack_ok;+}++int git_push_status_foreach(git_push *push,+ int (*cb)(const char *ref, const char *msg, void *data),+ void *data)+{+ push_status *status;+ unsigned int i;++ git_vector_foreach(&push->status, i, status) {+ if (cb(status->ref, status->msg, data) < 0)+ return GIT_EUSER;+ }++ return 0;+}++void git_push_status_free(push_status *status)+{+ if (status == NULL)+ return;++ if (status->msg)+ git__free(status->msg);++ git__free(status->ref);+ git__free(status);+}++void git_push_free(git_push *push)+{+ push_spec *spec;+ push_status *status;+ unsigned int i;++ if (push == NULL)+ return;++ git_vector_foreach(&push->specs, i, spec) {+ free_refspec(spec);+ }+ git_vector_free(&push->specs);++ git_vector_foreach(&push->status, i, status) {+ git_push_status_free(status);+ }+ git_vector_free(&push->status);++ git__free(push);+}
+ libgit2/src/push.h view
@@ -0,0 +1,51 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_push_h__+#define INCLUDE_push_h__++#include "git2.h"++typedef struct push_spec {+ char *lref;+ char *rref;++ git_oid loid;+ git_oid roid;++ bool force;+} push_spec;++typedef struct push_status {+ bool ok;++ char *ref;+ char *msg;+} push_status;++struct git_push {+ git_repository *repo;+ git_packbuilder *pb;+ git_remote *remote;+ git_vector specs;+ bool report_status;++ /* report-status */+ bool unpack_ok;+ git_vector status;++ /* options */+ unsigned pb_parallelism;+};++/**+ * Free the given push status object+ *+ * @param status The push status object+ */+void git_push_status_free(push_status *status);++#endif
+ libgit2/src/refdb.c view
@@ -0,0 +1,185 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "common.h"+#include "posix.h"+#include "git2/object.h"+#include "git2/refs.h"+#include "git2/refdb.h"+#include "hash.h"+#include "refdb.h"+#include "refs.h"++#include "git2/refdb_backend.h"++int git_refdb_new(git_refdb **out, git_repository *repo)+{+ git_refdb *db;++ assert(out && repo);++ db = git__calloc(1, sizeof(*db));+ GITERR_CHECK_ALLOC(db);++ db->repo = repo;++ *out = db;+ GIT_REFCOUNT_INC(db);+ return 0;+}++int git_refdb_open(git_refdb **out, git_repository *repo)+{+ git_refdb *db;+ git_refdb_backend *dir;++ assert(out && repo);++ *out = NULL;++ if (git_refdb_new(&db, repo) < 0)+ return -1;++ /* Add the default (filesystem) backend */+ if (git_refdb_backend_fs(&dir, repo, db) < 0) {+ git_refdb_free(db);+ return -1;+ }++ db->repo = repo;+ db->backend = dir;++ *out = db;+ return 0;+}++int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend)+{+ if (db->backend) {+ if(db->backend->free)+ db->backend->free(db->backend);+ else+ git__free(db->backend);+ }++ db->backend = backend;++ return 0;+}++int git_refdb_compress(git_refdb *db)+{+ assert(db);+ + if (db->backend->compress) {+ return db->backend->compress(db->backend);+ }+ + return 0;+}++static void refdb_free(git_refdb *db)+{+ if (db->backend) {+ if(db->backend->free)+ db->backend->free(db->backend);+ else+ git__free(db->backend);+ }++ git__free(db);+}++void git_refdb_free(git_refdb *db)+{+ if (db == NULL)+ return;++ GIT_REFCOUNT_DEC(db, refdb_free);+}++int git_refdb_exists(int *exists, git_refdb *refdb, const char *ref_name)+{+ assert(exists && refdb && refdb->backend);++ return refdb->backend->exists(exists, refdb->backend, ref_name);+}++int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name)+{+ assert(db && db->backend && ref_name);++ return db->backend->lookup(out, db->backend, ref_name);+}++int git_refdb_foreach(+ git_refdb *db,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload)+{+ assert(db && db->backend);++ return db->backend->foreach(db->backend, list_flags, callback, payload);+}++struct glob_cb_data {+ const char *glob;+ git_reference_foreach_cb callback;+ void *payload;+};++static int fromglob_cb(const char *reference_name, void *payload)+{+ struct glob_cb_data *data = (struct glob_cb_data *)payload;++ if (!p_fnmatch(data->glob, reference_name, 0))+ return data->callback(reference_name, data->payload);++ return 0;+}++int git_refdb_foreach_glob(+ git_refdb *db,+ const char *glob,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload)+{+ int error;+ struct glob_cb_data data;++ assert(db && db->backend && glob && callback);++ if(db->backend->foreach_glob != NULL)+ error = db->backend->foreach_glob(db->backend,+ glob, list_flags, callback, payload);+ else {+ data.glob = glob;+ data.callback = callback;+ data.payload = payload;++ error = db->backend->foreach(db->backend,+ list_flags, fromglob_cb, &data);+ }++ return error;+}++int git_refdb_write(git_refdb *db, const git_reference *ref)+{+ assert(db && db->backend);++ return db->backend->write(db->backend, ref);+}++int git_refdb_delete(struct git_refdb *db, const git_reference *ref)+{+ assert(db && db->backend);++ return db->backend->delete(db->backend, ref);+}
+ libgit2/src/refdb.h view
@@ -0,0 +1,46 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_refdb_h__+#define INCLUDE_refdb_h__++#include "git2/refdb.h"+#include "repository.h"++struct git_refdb {+ git_refcount rc;+ git_repository *repo;+ git_refdb_backend *backend;+};++int git_refdb_exists(+ int *exists,+ git_refdb *refdb,+ const char *ref_name);++int git_refdb_lookup(+ git_reference **out,+ git_refdb *refdb,+ const char *ref_name);++int git_refdb_foreach(+ git_refdb *refdb,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload);++int git_refdb_foreach_glob(+ git_refdb *refdb,+ const char *glob,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload);++int git_refdb_write(git_refdb *refdb, const git_reference *ref);++int git_refdb_delete(struct git_refdb *refdb, const git_reference *ref);++#endif
+ libgit2/src/refdb_fs.c view
@@ -0,0 +1,1023 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "refs.h"+#include "hash.h"+#include "repository.h"+#include "fileops.h"+#include "pack.h"+#include "reflog.h"+#include "config.h"+#include "refdb.h"+#include "refdb_fs.h"++#include <git2/tag.h>+#include <git2/object.h>+#include <git2/refdb.h>+#include <git2/refdb_backend.h>++GIT__USE_STRMAP;++#define DEFAULT_NESTING_LEVEL 5+#define MAX_NESTING_LEVEL 10++enum {+ GIT_PACKREF_HAS_PEEL = 1,+ GIT_PACKREF_WAS_LOOSE = 2+};++struct packref {+ git_oid oid;+ git_oid peel;+ char flags;+ char name[GIT_FLEX_ARRAY];+};++typedef struct refdb_fs_backend {+ git_refdb_backend parent;++ git_repository *repo;+ const char *path;+ git_refdb *refdb;++ git_refcache refcache;+} refdb_fs_backend;++static int reference_read(+ git_buf *file_content,+ time_t *mtime,+ const char *repo_path,+ const char *ref_name,+ int *updated)+{+ git_buf path = GIT_BUF_INIT;+ int result;++ assert(file_content && repo_path && ref_name);++ /* Determine the full path of the file */+ if (git_buf_joinpath(&path, repo_path, ref_name) < 0)+ return -1;+ + result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, NULL, updated);+ git_buf_free(&path);++ return result;+}++static int packed_parse_oid(+ struct packref **ref_out,+ const char **buffer_out,+ const char *buffer_end)+{+ struct packref *ref = NULL;++ const char *buffer = *buffer_out;+ const char *refname_begin, *refname_end;++ size_t refname_len;+ git_oid id;++ refname_begin = (buffer + GIT_OID_HEXSZ + 1);+ if (refname_begin >= buffer_end || refname_begin[-1] != ' ')+ goto corrupt;++ /* Is this a valid object id? */+ if (git_oid_fromstr(&id, buffer) < 0)+ goto corrupt;++ refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);+ if (refname_end == NULL)+ refname_end = buffer_end;++ if (refname_end[-1] == '\r')+ refname_end--;++ refname_len = refname_end - refname_begin;++ ref = git__malloc(sizeof(struct packref) + refname_len + 1);+ GITERR_CHECK_ALLOC(ref);++ memcpy(ref->name, refname_begin, refname_len);+ ref->name[refname_len] = 0;++ git_oid_cpy(&ref->oid, &id);++ ref->flags = 0;++ *ref_out = ref;+ *buffer_out = refname_end + 1;++ return 0;++corrupt:+ git__free(ref);+ giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");+ return -1;+}++static int packed_parse_peel(+ struct packref *tag_ref,+ const char **buffer_out,+ const char *buffer_end)+{+ const char *buffer = *buffer_out + 1;++ assert(buffer[-1] == '^');++ /* Ensure it's not the first entry of the file */+ if (tag_ref == NULL)+ goto corrupt;++ /* Ensure reference is a tag */+ if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)+ goto corrupt;++ if (buffer + GIT_OID_HEXSZ > buffer_end)+ goto corrupt;++ /* Is this a valid object id? */+ if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)+ goto corrupt;++ buffer = buffer + GIT_OID_HEXSZ;+ if (*buffer == '\r')+ buffer++;++ if (buffer != buffer_end) {+ if (*buffer == '\n')+ buffer++;+ else+ goto corrupt;+ }++ *buffer_out = buffer;+ return 0;++corrupt:+ giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");+ return -1;+}++static int packed_load(refdb_fs_backend *backend)+{+ int result, updated;+ git_buf packfile = GIT_BUF_INIT;+ const char *buffer_start, *buffer_end;+ git_refcache *ref_cache = &backend->refcache;++ /* First we make sure we have allocated the hash table */+ if (ref_cache->packfile == NULL) {+ ref_cache->packfile = git_strmap_alloc();+ GITERR_CHECK_ALLOC(ref_cache->packfile);+ }+ + result = reference_read(&packfile, &ref_cache->packfile_time,+ backend->path, GIT_PACKEDREFS_FILE, &updated);++ /*+ * If we couldn't find the file, we need to clear the table and+ * return. On any other error, we return that error. If everything+ * went fine and the file wasn't updated, then there's nothing new+ * for us here, so just return. Anything else means we need to+ * refresh the packed refs.+ */+ if (result == GIT_ENOTFOUND) {+ git_strmap_clear(ref_cache->packfile);+ return 0;+ }++ if (result < 0)+ return -1;+ + if (!updated)+ return 0;++ /*+ * At this point, we want to refresh the packed refs. We already+ * have the contents in our buffer.+ */+ git_strmap_clear(ref_cache->packfile);++ buffer_start = (const char *)packfile.ptr;+ buffer_end = (const char *)(buffer_start) + packfile.size;++ while (buffer_start < buffer_end && buffer_start[0] == '#') {+ buffer_start = strchr(buffer_start, '\n');+ if (buffer_start == NULL)+ goto parse_failed;++ buffer_start++;+ }++ while (buffer_start < buffer_end) {+ int err;+ struct packref *ref = NULL;++ if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)+ goto parse_failed;++ if (buffer_start[0] == '^') {+ if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)+ goto parse_failed;+ }++ git_strmap_insert(ref_cache->packfile, ref->name, ref, err);+ if (err < 0)+ goto parse_failed;+ }++ git_buf_free(&packfile);+ return 0;++parse_failed:+ git_strmap_free(ref_cache->packfile);+ ref_cache->packfile = NULL;+ git_buf_free(&packfile);+ return -1;+}++static int loose_parse_oid(git_oid *oid, git_buf *file_content)+{+ size_t len;+ const char *str;++ len = git_buf_len(file_content);+ if (len < GIT_OID_HEXSZ)+ goto corrupted;++ /* str is guranteed to be zero-terminated */+ str = git_buf_cstr(file_content);++ /* we need to get 40 OID characters from the file */+ if (git_oid_fromstr(oid, git_buf_cstr(file_content)) < 0)+ goto corrupted;++ /* If the file is longer than 40 chars, the 41st must be a space */+ str += GIT_OID_HEXSZ;+ if (*str == '\0' || git__isspace(*str))+ return 0;++corrupted:+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");+ return -1;+}++static int loose_lookup_to_packfile(+ struct packref **ref_out,+ refdb_fs_backend *backend,+ const char *name)+{+ git_buf ref_file = GIT_BUF_INIT;+ struct packref *ref = NULL;+ size_t name_len;++ *ref_out = NULL;++ if (reference_read(&ref_file, NULL, backend->path, name, NULL) < 0)+ return -1;++ git_buf_rtrim(&ref_file);++ name_len = strlen(name);+ ref = git__malloc(sizeof(struct packref) + name_len + 1);+ GITERR_CHECK_ALLOC(ref);++ memcpy(ref->name, name, name_len);+ ref->name[name_len] = 0;++ if (loose_parse_oid(&ref->oid, &ref_file) < 0) {+ git_buf_free(&ref_file);+ git__free(ref);+ return -1;+ }++ ref->flags = GIT_PACKREF_WAS_LOOSE;++ *ref_out = ref;+ git_buf_free(&ref_file);+ return 0;+}+++static int _dirent_loose_load(void *data, git_buf *full_path)+{+ refdb_fs_backend *backend = (refdb_fs_backend *)data;+ void *old_ref = NULL;+ struct packref *ref;+ const char *file_path;+ int err;++ if (git_path_isdir(full_path->ptr) == true)+ return git_path_direach(full_path, _dirent_loose_load, backend);++ file_path = full_path->ptr + strlen(backend->path);++ if (loose_lookup_to_packfile(&ref, backend, file_path) < 0)+ return -1;++ git_strmap_insert2(+ backend->refcache.packfile, ref->name, ref, old_ref, err);+ if (err < 0) {+ git__free(ref);+ return -1;+ }++ git__free(old_ref);+ return 0;+}++/*+ * Load all the loose references from the repository+ * into the in-memory Packfile, and build a vector with+ * all the references so it can be written back to+ * disk.+ */+static int packed_loadloose(refdb_fs_backend *backend)+{+ git_buf refs_path = GIT_BUF_INIT;+ int result;++ /* the packfile must have been previously loaded! */+ assert(backend->refcache.packfile);++ if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)+ return -1;++ /*+ * Load all the loose files from disk into the Packfile table.+ * This will overwrite any old packed entries with their+ * updated loose versions+ */+ result = git_path_direach(&refs_path, _dirent_loose_load, backend);+ git_buf_free(&refs_path);++ return result;+}++static int refdb_fs_backend__exists(+ int *exists,+ git_refdb_backend *_backend,+ const char *ref_name)+{+ refdb_fs_backend *backend;+ git_buf ref_path = GIT_BUF_INIT;++ assert(_backend);+ backend = (refdb_fs_backend *)_backend;++ if (packed_load(backend) < 0)+ return -1;++ if (git_buf_joinpath(&ref_path, backend->path, ref_name) < 0)+ return -1;++ if (git_path_isfile(ref_path.ptr) == true ||+ git_strmap_exists(backend->refcache.packfile, ref_path.ptr))+ *exists = 1;+ else+ *exists = 0;++ git_buf_free(&ref_path);+ return 0;+}++static const char *loose_parse_symbolic(git_buf *file_content)+{+ const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);+ const char *refname_start;++ refname_start = (const char *)file_content->ptr;++ if (git_buf_len(file_content) < header_len + 1) {+ giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");+ return NULL;+ }++ /*+ * Assume we have already checked for the header+ * before calling this function+ */+ refname_start += header_len;++ return refname_start;+}++static int loose_lookup(+ git_reference **out,+ refdb_fs_backend *backend,+ const char *ref_name)+{+ const char *target;+ git_oid oid;+ git_buf ref_file = GIT_BUF_INIT;+ int error = 0;++ error = reference_read(&ref_file, NULL, backend->path, ref_name, NULL);++ if (error < 0)+ goto done;++ if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {+ git_buf_rtrim(&ref_file);++ if ((target = loose_parse_symbolic(&ref_file)) == NULL) {+ error = -1;+ goto done;+ }++ *out = git_reference__alloc(backend->refdb, ref_name, NULL, target);+ } else {+ if ((error = loose_parse_oid(&oid, &ref_file)) < 0)+ goto done;+ + *out = git_reference__alloc(backend->refdb, ref_name, &oid, NULL);+ }++ if (*out == NULL)+ error = -1;++done:+ git_buf_free(&ref_file);+ return error;+}++static int packed_map_entry(+ struct packref **entry,+ khiter_t *pos,+ refdb_fs_backend *backend,+ const char *ref_name)+{+ git_strmap *packfile_refs;++ if (packed_load(backend) < 0)+ return -1;+ + /* Look up on the packfile */+ packfile_refs = backend->refcache.packfile;++ *pos = git_strmap_lookup_index(packfile_refs, ref_name);+ + if (!git_strmap_valid_index(packfile_refs, *pos)) {+ giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref_name);+ return GIT_ENOTFOUND;+ }++ *entry = git_strmap_value_at(packfile_refs, *pos);+ + return 0;+}++static int packed_lookup(+ git_reference **out,+ refdb_fs_backend *backend,+ const char *ref_name)+{+ struct packref *entry;+ khiter_t pos;+ int error = 0;+ + if ((error = packed_map_entry(&entry, &pos, backend, ref_name)) < 0)+ return error;++ if ((*out = git_reference__alloc(backend->refdb, ref_name, &entry->oid, NULL)) == NULL)+ return -1;+ + return 0;+}++static int refdb_fs_backend__lookup(+ git_reference **out,+ git_refdb_backend *_backend,+ const char *ref_name)+{+ refdb_fs_backend *backend;+ int result;++ assert(_backend);++ backend = (refdb_fs_backend *)_backend;++ if ((result = loose_lookup(out, backend, ref_name)) == 0)+ return 0;++ /* only try to lookup this reference on the packfile if it+ * wasn't found on the loose refs; not if there was a critical error */+ if (result == GIT_ENOTFOUND) {+ giterr_clear();+ result = packed_lookup(out, backend, ref_name);+ }++ return result;+}++struct dirent_list_data {+ refdb_fs_backend *backend;+ size_t repo_path_len;+ unsigned int list_type:2;++ git_reference_foreach_cb callback;+ void *callback_payload;+ int callback_error;+};++static git_ref_t loose_guess_rtype(const git_buf *full_path)+{+ git_buf ref_file = GIT_BUF_INIT;+ git_ref_t type;++ type = GIT_REF_INVALID;++ if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {+ if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)+ type = GIT_REF_SYMBOLIC;+ else+ type = GIT_REF_OID;+ }++ git_buf_free(&ref_file);+ return type;+}++static int _dirent_loose_listall(void *_data, git_buf *full_path)+{+ struct dirent_list_data *data = (struct dirent_list_data *)_data;+ const char *file_path = full_path->ptr + data->repo_path_len;++ if (git_path_isdir(full_path->ptr) == true)+ return git_path_direach(full_path, _dirent_loose_listall, _data);++ /* do not add twice a reference that exists already in the packfile */+ if (git_strmap_exists(data->backend->refcache.packfile, file_path))+ return 0;++ if (data->list_type != GIT_REF_LISTALL) {+ if ((data->list_type & loose_guess_rtype(full_path)) == 0)+ return 0; /* we are filtering out this reference */+ }++ /* Locked references aren't returned */+ if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION))+ return 0;++ if (data->callback(file_path, data->callback_payload))+ data->callback_error = GIT_EUSER;++ return data->callback_error;+}++static int refdb_fs_backend__foreach(+ git_refdb_backend *_backend,+ unsigned int list_type,+ git_reference_foreach_cb callback,+ void *payload)+{+ refdb_fs_backend *backend;+ int result;+ struct dirent_list_data data;+ git_buf refs_path = GIT_BUF_INIT;+ const char *ref_name;+ void *ref = NULL;+ + GIT_UNUSED(ref);++ assert(_backend);+ backend = (refdb_fs_backend *)_backend;++ if (packed_load(backend) < 0)+ return -1;+ + /* list all the packed references first */+ if (list_type & GIT_REF_OID) {+ git_strmap_foreach(backend->refcache.packfile, ref_name, ref, {+ if (callback(ref_name, payload))+ return GIT_EUSER;+ });+ }++ /* now list the loose references, trying not to+ * duplicate the ref names already in the packed-refs file */++ data.repo_path_len = strlen(backend->path);+ data.list_type = list_type;+ data.backend = backend;+ data.callback = callback;+ data.callback_payload = payload;+ data.callback_error = 0;++ if (git_buf_joinpath(&refs_path, backend->path, GIT_REFS_DIR) < 0)+ return -1;++ result = git_path_direach(&refs_path, _dirent_loose_listall, &data);++ git_buf_free(&refs_path);++ return data.callback_error ? GIT_EUSER : result;+}++static int loose_write(refdb_fs_backend *backend, const git_reference *ref)+{+ git_filebuf file = GIT_FILEBUF_INIT;+ git_buf ref_path = GIT_BUF_INIT;++ /* Remove a possibly existing empty directory hierarchy+ * which name would collide with the reference name+ */+ if (git_futils_rmdir_r(ref->name, backend->path,+ GIT_RMDIR_SKIP_NONEMPTY) < 0)+ return -1;++ if (git_buf_joinpath(&ref_path, backend->path, ref->name) < 0)+ return -1;++ if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {+ git_buf_free(&ref_path);+ return -1;+ }++ git_buf_free(&ref_path);++ if (ref->type == GIT_REF_OID) {+ char oid[GIT_OID_HEXSZ + 1];++ git_oid_fmt(oid, &ref->target.oid);+ oid[GIT_OID_HEXSZ] = '\0';++ git_filebuf_printf(&file, "%s\n", oid);++ } else if (ref->type == GIT_REF_SYMBOLIC) {+ git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);+ } else {+ assert(0); /* don't let this happen */+ }++ return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);+}++static int packed_sort(const void *a, const void *b)+{+ const struct packref *ref_a = (const struct packref *)a;+ const struct packref *ref_b = (const struct packref *)b;++ return strcmp(ref_a->name, ref_b->name);+}++/*+ * Find out what object this reference resolves to.+ *+ * For references that point to a 'big' tag (e.g. an+ * actual tag object on the repository), we need to+ * cache on the packfile the OID of the object to+ * which that 'big tag' is pointing to.+ */+static int packed_find_peel(refdb_fs_backend *backend, struct packref *ref)+{+ git_object *object;++ if (ref->flags & GIT_PACKREF_HAS_PEEL)+ return 0;++ /*+ * Only applies to tags, i.e. references+ * in the /refs/tags folder+ */+ if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)+ return 0;++ /*+ * Find the tagged object in the repository+ */+ if (git_object_lookup(&object, backend->repo, &ref->oid, GIT_OBJ_ANY) < 0)+ return -1;++ /*+ * If the tagged object is a Tag object, we need to resolve it;+ * if the ref is actually a 'weak' ref, we don't need to resolve+ * anything.+ */+ if (git_object_type(object) == GIT_OBJ_TAG) {+ git_tag *tag = (git_tag *)object;++ /*+ * Find the object pointed at by this tag+ */+ git_oid_cpy(&ref->peel, git_tag_target_id(tag));+ ref->flags |= GIT_PACKREF_HAS_PEEL;++ /*+ * The reference has now cached the resolved OID, and is+ * marked at such. When written to the packfile, it'll be+ * accompanied by this resolved oid+ */+ }++ git_object_free(object);+ return 0;+}++/*+ * Write a single reference into a packfile+ */+static int packed_write_ref(struct packref *ref, git_filebuf *file)+{+ char oid[GIT_OID_HEXSZ + 1];++ git_oid_fmt(oid, &ref->oid);+ oid[GIT_OID_HEXSZ] = 0;++ /*+ * For references that peel to an object in the repo, we must+ * write the resulting peel on a separate line, e.g.+ *+ * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4+ * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100+ *+ * This obviously only applies to tags.+ * The required peels have already been loaded into `ref->peel_target`.+ */+ if (ref->flags & GIT_PACKREF_HAS_PEEL) {+ char peel[GIT_OID_HEXSZ + 1];+ git_oid_fmt(peel, &ref->peel);+ peel[GIT_OID_HEXSZ] = 0;++ if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)+ return -1;+ } else {+ if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)+ return -1;+ }++ return 0;+}++/*+ * Remove all loose references+ *+ * Once we have successfully written a packfile,+ * all the loose references that were packed must be+ * removed from disk.+ *+ * This is a dangerous method; make sure the packfile+ * is well-written, because we are destructing references+ * here otherwise.+ */+static int packed_remove_loose(+ refdb_fs_backend *backend,+ git_vector *packing_list)+{+ size_t i;+ git_buf full_path = GIT_BUF_INIT;+ int failed = 0;++ for (i = 0; i < packing_list->length; ++i) {+ struct packref *ref = git_vector_get(packing_list, i);++ if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)+ continue;++ if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0)+ return -1; /* critical; do not try to recover on oom */++ if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {+ if (failed)+ continue;++ giterr_set(GITERR_REFERENCE,+ "Failed to remove loose reference '%s' after packing: %s",+ full_path.ptr, strerror(errno));++ failed = 1;+ }++ /*+ * if we fail to remove a single file, this is *not* good,+ * but we should keep going and remove as many as possible.+ * After we've removed as many files as possible, we return+ * the error code anyway.+ */+ }++ git_buf_free(&full_path);+ return failed ? -1 : 0;+}++/*+ * Write all the contents in the in-memory packfile to disk.+ */+static int packed_write(refdb_fs_backend *backend)+{+ git_filebuf pack_file = GIT_FILEBUF_INIT;+ size_t i;+ git_buf pack_file_path = GIT_BUF_INIT;+ git_vector packing_list;+ unsigned int total_refs;++ assert(backend && backend->refcache.packfile);++ total_refs =+ (unsigned int)git_strmap_num_entries(backend->refcache.packfile);++ if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)+ return -1;++ /* Load all the packfile into a vector */+ {+ struct packref *reference;++ /* cannot fail: vector already has the right size */+ git_strmap_foreach_value(backend->refcache.packfile, reference, {+ git_vector_insert(&packing_list, reference);+ });+ }++ /* sort the vector so the entries appear sorted on the packfile */+ git_vector_sort(&packing_list);++ /* Now we can open the file! */+ if (git_buf_joinpath(&pack_file_path,+ backend->path, GIT_PACKEDREFS_FILE) < 0)+ goto cleanup_memory;++ if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)+ goto cleanup_packfile;++ /* Packfiles have a header... apparently+ * This is in fact not required, but we might as well print it+ * just for kicks */+ if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)+ goto cleanup_packfile;++ for (i = 0; i < packing_list.length; ++i) {+ struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);++ if (packed_find_peel(backend, ref) < 0)+ goto cleanup_packfile;++ if (packed_write_ref(ref, &pack_file) < 0)+ goto cleanup_packfile;+ }++ /* if we've written all the references properly, we can commit+ * the packfile to make the changes effective */+ if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)+ goto cleanup_memory;++ /* when and only when the packfile has been properly written,+ * we can go ahead and remove the loose refs */+ if (packed_remove_loose(backend, &packing_list) < 0)+ goto cleanup_memory;++ {+ struct stat st;+ if (p_stat(pack_file_path.ptr, &st) == 0)+ backend->refcache.packfile_time = st.st_mtime;+ }++ git_vector_free(&packing_list);+ git_buf_free(&pack_file_path);++ /* we're good now */+ return 0;++cleanup_packfile:+ git_filebuf_cleanup(&pack_file);++cleanup_memory:+ git_vector_free(&packing_list);+ git_buf_free(&pack_file_path);++ return -1;+}++static int refdb_fs_backend__write(+ git_refdb_backend *_backend,+ const git_reference *ref)+{+ refdb_fs_backend *backend;++ assert(_backend);+ backend = (refdb_fs_backend *)_backend;++ return loose_write(backend, ref);+}++static int refdb_fs_backend__delete(+ git_refdb_backend *_backend,+ const git_reference *ref)+{+ refdb_fs_backend *backend;+ git_repository *repo;+ git_buf loose_path = GIT_BUF_INIT;+ struct packref *pack_ref;+ khiter_t pack_ref_pos;+ int error = 0, pack_error;+ bool loose_deleted;++ assert(_backend);+ assert(ref);++ backend = (refdb_fs_backend *)_backend;+ repo = backend->repo;++ /* If a loose reference exists, remove it from the filesystem */+ + if (git_buf_joinpath(&loose_path, repo->path_repository, ref->name) < 0)+ return -1;++ if (git_path_isfile(loose_path.ptr)) {+ error = p_unlink(loose_path.ptr);+ loose_deleted = 1;+ }+ + git_buf_free(&loose_path);++ if (error != 0)+ return error;++ /* If a packed reference exists, remove it from the packfile and repack */++ if ((pack_error = packed_map_entry(&pack_ref, &pack_ref_pos, backend, ref->name)) == 0) {+ git_strmap_delete_at(backend->refcache.packfile, pack_ref_pos);+ git__free(pack_ref);++ error = packed_write(backend);+ }+ + if (pack_error == GIT_ENOTFOUND)+ error = loose_deleted ? 0 : GIT_ENOTFOUND;+ else+ error = pack_error;++ return error;+}++static int refdb_fs_backend__compress(git_refdb_backend *_backend)+{+ refdb_fs_backend *backend;++ assert(_backend);+ backend = (refdb_fs_backend *)_backend;++ if (packed_load(backend) < 0 || /* load the existing packfile */+ packed_loadloose(backend) < 0 || /* add all the loose refs */+ packed_write(backend) < 0) /* write back to disk */+ return -1;++ return 0;+}++static void refcache_free(git_refcache *refs)+{+ assert(refs);++ if (refs->packfile) {+ struct packref *reference;++ git_strmap_foreach_value(refs->packfile, reference, {+ git__free(reference);+ });++ git_strmap_free(refs->packfile);+ }+}++static void refdb_fs_backend__free(git_refdb_backend *_backend)+{+ refdb_fs_backend *backend;++ assert(_backend);+ backend = (refdb_fs_backend *)_backend;++ refcache_free(&backend->refcache);+ git__free(backend);+}++int git_refdb_backend_fs(+ git_refdb_backend **backend_out,+ git_repository *repository,+ git_refdb *refdb)+{+ refdb_fs_backend *backend;++ backend = git__calloc(1, sizeof(refdb_fs_backend));+ GITERR_CHECK_ALLOC(backend);++ backend->repo = repository;+ backend->path = repository->path_repository;+ backend->refdb = refdb;++ backend->parent.exists = &refdb_fs_backend__exists;+ backend->parent.lookup = &refdb_fs_backend__lookup;+ backend->parent.foreach = &refdb_fs_backend__foreach;+ backend->parent.write = &refdb_fs_backend__write;+ backend->parent.delete = &refdb_fs_backend__delete;+ backend->parent.compress = &refdb_fs_backend__compress;+ backend->parent.free = &refdb_fs_backend__free;++ *backend_out = (git_refdb_backend *)backend;+ return 0;+}
+ libgit2/src/refdb_fs.h view
@@ -0,0 +1,15 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_refdb_fs_h__+#define INCLUDE_refdb_fs_h__++typedef struct {+ git_strmap *packfile;+ time_t packfile_time;+} git_refcache;++#endif
libgit2/src/reflog.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,7 +10,7 @@ #include "filebuf.h" #include "signature.h" -static int reflog_init(git_reflog **reflog, git_reference *ref)+static int reflog_init(git_reflog **reflog, const git_reference *ref) { git_reflog *log; @@ -28,68 +28,70 @@ return -1; } + log->owner = git_reference_owner(ref); *reflog = log; return 0; } -static int reflog_write(const char *log_path, const char *oid_old,- const char *oid_new, const git_signature *committer,- const char *msg)+static int serialize_reflog_entry(+ git_buf *buf,+ const git_oid *oid_old,+ const git_oid *oid_new,+ const git_signature *committer,+ const char *msg) {- int error;- git_buf log = GIT_BUF_INIT;- git_filebuf fbuf = GIT_FILEBUF_INIT;- bool trailing_newline = false;+ char raw_old[GIT_OID_HEXSZ+1];+ char raw_new[GIT_OID_HEXSZ+1]; - assert(log_path && oid_old && oid_new && committer);+ git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old);+ git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); - if (msg) {- const char *newline = strchr(msg, '\n');- if (newline) {- if (*(newline + 1) == '\0')- trailing_newline = true;- else {- giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");- return -1;- }- }- }+ git_buf_clear(buf); - git_buf_puts(&log, oid_old);- git_buf_putc(&log, ' ');+ git_buf_puts(buf, raw_old);+ git_buf_putc(buf, ' ');+ git_buf_puts(buf, raw_new); - git_buf_puts(&log, oid_new);+ git_signature__writebuf(buf, " ", committer); - git_signature__writebuf(&log, " ", committer);- git_buf_truncate(&log, log.size - 1); /* drop LF */+ /* drop trailing LF */+ git_buf_rtrim(buf); if (msg) {- git_buf_putc(&log, '\t');- git_buf_puts(&log, msg);+ git_buf_putc(buf, '\t');+ git_buf_puts(buf, msg); } - if (!trailing_newline)- git_buf_putc(&log, '\n');+ git_buf_putc(buf, '\n'); - if (git_buf_oom(&log)) {- git_buf_free(&log);- return -1;- }+ return git_buf_oom(buf);+} - error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND);- if (!error) {- if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)- git_filebuf_cleanup(&fbuf);- else- error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE);- }+static int reflog_entry_new(git_reflog_entry **entry)+{+ git_reflog_entry *e; - git_buf_free(&log);+ assert(entry); - return error;+ e = git__malloc(sizeof(git_reflog_entry));+ GITERR_CHECK_ALLOC(e);++ memset(e, 0, sizeof(git_reflog_entry));++ *entry = e;++ return 0; } +static void reflog_entry_free(git_reflog_entry *entry)+{+ git_signature_free(entry->committer);++ git__free(entry->msg);+ git__free(entry);+}+ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) { const char *ptr;@@ -105,8 +107,8 @@ } while (0) while (buf_size > GIT_REFLOG_SIZE_MIN) {- entry = git__malloc(sizeof(git_reflog_entry));- GITERR_CHECK_ALLOC(entry);+ if (reflog_entry_new(&entry) < 0)+ return -1; entry->committer = git__malloc(sizeof(git_signature)); GITERR_CHECK_ALLOC(entry->committer);@@ -153,25 +155,24 @@ #undef seek_forward fail:- if (entry) {- git__free(entry->committer);- git__free(entry);- }+ if (entry)+ reflog_entry_free(entry);+ return -1; } void git_reflog_free(git_reflog *reflog) {- unsigned int i;+ size_t i; git_reflog_entry *entry; + if (reflog == NULL)+ return;+ for (i=0; i < reflog->entries.length; i++) { entry = git_vector_get(&reflog->entries, i); - git_signature_free(entry->committer);-- git__free(entry->msg);- git__free(entry);+ reflog_entry_free(entry); } git_vector_free(&reflog->entries);@@ -179,110 +180,230 @@ git__free(reflog); } -int git_reflog_read(git_reflog **reflog, git_reference *ref)+static int retrieve_reflog_path(git_buf *path, const git_reference *ref) {- int error;+ return git_buf_join_n(path, '/', 3,+ git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name);+}++static int create_new_reflog_file(const char *filepath)+{+ int fd, error;++ if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0)+ return error;++ if ((fd = p_open(filepath,+ O_WRONLY | O_CREAT | O_TRUNC,+ GIT_REFLOG_FILE_MODE)) < 0)+ return -1;++ return p_close(fd);+}++int git_reflog_read(git_reflog **reflog, const git_reference *ref)+{+ int error = -1; git_buf log_path = GIT_BUF_INIT; git_buf log_file = GIT_BUF_INIT; git_reflog *log = NULL; + assert(reflog && ref);+ *reflog = NULL; if (reflog_init(&log, ref) < 0) return -1; - error = git_buf_join_n(&log_path, '/', 3,- ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);+ if (retrieve_reflog_path(&log_path, ref) < 0)+ goto cleanup; - if (!error)- error = git_futils_readbuffer(&log_file, log_path.ptr);+ error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path));+ if (error < 0 && error != GIT_ENOTFOUND)+ goto cleanup; - if (!error)- error = reflog_parse(log, log_file.ptr, log_file.size);+ if ((error == GIT_ENOTFOUND) &&+ ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0))+ goto cleanup; - if (!error)- *reflog = log;- else- git_reflog_free(log);+ if ((error = reflog_parse(log,+ git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0)+ goto cleanup; + *reflog = log;+ goto success;++cleanup:+ git_reflog_free(log);++success: git_buf_free(&log_file); git_buf_free(&log_path); return error; } -int git_reflog_write(git_reference *ref, const git_oid *oid_old,- const git_signature *committer, const char *msg)+int git_reflog_write(git_reflog *reflog) {- int error;- char old[GIT_OID_HEXSZ+1];- char new[GIT_OID_HEXSZ+1];+ int error = -1;+ unsigned int i;+ git_reflog_entry *entry; git_buf log_path = GIT_BUF_INIT;- git_reference *r;- const git_oid *oid;+ git_buf log = GIT_BUF_INIT;+ git_filebuf fbuf = GIT_FILEBUF_INIT; - if ((error = git_reference_resolve(&r, ref)) < 0)- return error;+ assert(reflog); - oid = git_reference_oid(r);- if (oid == NULL) {- giterr_set(GITERR_REFERENCE,- "Failed to write reflog. Cannot resolve reference `%s`", r->name);- git_reference_free(r);+ if (git_buf_join_n(&log_path, '/', 3,+ git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0) return -1;++ if (!git_path_isfile(git_buf_cstr(&log_path))) {+ giterr_set(GITERR_INVALID,+ "Log file for reference '%s' doesn't exist.", reflog->ref_name);+ goto cleanup; } - git_oid_tostr(new, GIT_OID_HEXSZ+1, oid);+ if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0)+ goto cleanup; - git_reference_free(r);+ git_vector_foreach(&reflog->entries, i, entry) {+ if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0)+ goto cleanup; - error = git_buf_join_n(&log_path, '/', 3,- ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);- if (error < 0)+ if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0)+ goto cleanup;+ }++ error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE);+ goto success;++cleanup:+ git_filebuf_cleanup(&fbuf);++success:+ git_buf_free(&log);+ git_buf_free(&log_path);+ return error;+}++int git_reflog_append(git_reflog *reflog, const git_oid *new_oid,+ const git_signature *committer, const char *msg)+{+ git_reflog_entry *entry;+ const git_reflog_entry *previous;+ const char *newline;++ assert(reflog && new_oid && committer);++ if (reflog_entry_new(&entry) < 0)+ return -1;++ if ((entry->committer = git_signature_dup(committer)) == NULL) goto cleanup; - if (git_path_exists(log_path.ptr) == false) {- error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE);- } else if (git_path_isfile(log_path.ptr) == false) {- giterr_set(GITERR_REFERENCE,- "Failed to write reflog. `%s` is directory", log_path.ptr);- error = -1;- } else if (oid_old == NULL) {- giterr_set(GITERR_REFERENCE,- "Failed to write reflog. Old OID cannot be NULL for existing reference");- error = -1;+ if (msg != NULL) {+ if ((entry->msg = git__strdup(msg)) == NULL)+ goto cleanup;++ newline = strchr(msg, '\n');++ if (newline) {+ if (newline[1] != '\0') {+ giterr_set(GITERR_INVALID, "Reflog message cannot contain newline");+ goto cleanup;+ }++ entry->msg[newline - msg] = '\0';+ } }- if (error < 0)- goto cleanup; - if (oid_old)- git_oid_tostr(old, sizeof(old), oid_old);+ previous = git_reflog_entry_byindex(reflog, 0);++ if (previous == NULL)+ git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); else- p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 0);+ git_oid_cpy(&entry->oid_old, &previous->oid_cur); - error = reflog_write(log_path.ptr, old, new, committer, msg);+ git_oid_cpy(&entry->oid_cur, new_oid); + if (git_vector_insert(&reflog->entries, entry) < 0)+ goto cleanup;++ return 0;+ cleanup:- git_buf_free(&log_path);- return error;+ reflog_entry_free(entry);+ return -1; } int git_reflog_rename(git_reference *ref, const char *new_name) {- int error;+ int error = 0, fd; git_buf old_path = GIT_BUF_INIT; git_buf new_path = GIT_BUF_INIT;+ git_buf temp_path = GIT_BUF_INIT;+ git_buf normalized = GIT_BUF_INIT; - if (!git_buf_join_n(&old_path, '/', 3, ref->owner->path_repository,- GIT_REFLOG_DIR, ref->name) &&- !git_buf_join_n(&new_path, '/', 3, ref->owner->path_repository,- GIT_REFLOG_DIR, new_name))- error = p_rename(git_buf_cstr(&old_path), git_buf_cstr(&new_path));- else+ assert(ref && new_name);++ if ((error = git_reference__normalize_name(+ &normalized, new_name, GIT_REF_FORMAT_ALLOW_ONELEVEL)) < 0)+ return error;++ if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0)+ return -1;++ if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0)+ return -1;++ if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), git_buf_cstr(&normalized)) < 0)+ return -1;++ /*+ * Move the reflog to a temporary place. This two-phase renaming is required+ * in order to cope with funny renaming use cases when one tries to move a reference+ * to a partially colliding namespace:+ * - a/b -> a/b/c+ * - a/b/c/d -> a/b/c+ */+ if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0)+ return -1;++ if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path))) < 0) { error = -1;+ goto cleanup;+ } + p_close(fd);++ if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) {+ giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);+ error = -1;+ goto cleanup;+ }++ if (git_path_isdir(git_buf_cstr(&new_path)) && + (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) {+ error = -1;+ goto cleanup;+ }++ if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) {+ error = -1;+ goto cleanup;+ }++ if (p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)) < 0) {+ giterr_set(GITERR_OS, "Failed to rename reflog for %s", new_name);+ error = -1;+ }++cleanup:+ git_buf_free(&temp_path); git_buf_free(&old_path); git_buf_free(&new_path);+ git_buf_free(&normalized); return error; }@@ -292,8 +413,7 @@ int error; git_buf path = GIT_BUF_INIT; - error = git_buf_join_n(- &path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name);+ error = retrieve_reflog_path(&path, ref); if (!error && git_path_exists(path.ptr)) error = p_unlink(path.ptr);@@ -303,38 +423,99 @@ return error; } -unsigned int git_reflog_entrycount(git_reflog *reflog)+size_t git_reflog_entrycount(git_reflog *reflog) { assert(reflog); return reflog->entries.length; } -const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx)+GIT_INLINE(size_t) reflog_inverse_index(size_t idx, size_t total) {+ return (total - 1) - idx;+}++const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx)+{ assert(reflog);- return git_vector_get(&reflog->entries, idx);++ if (idx >= reflog->entries.length)+ return NULL;++ return git_vector_get(+ &reflog->entries, reflog_inverse_index(idx, reflog->entries.length)); } -const git_oid * git_reflog_entry_oidold(const git_reflog_entry *entry)+const git_oid * git_reflog_entry_id_old(const git_reflog_entry *entry) { assert(entry); return &entry->oid_old; } -const git_oid * git_reflog_entry_oidnew(const git_reflog_entry *entry)+const git_oid * git_reflog_entry_id_new(const git_reflog_entry *entry) { assert(entry); return &entry->oid_cur; } -git_signature * git_reflog_entry_committer(const git_reflog_entry *entry)+const git_signature * git_reflog_entry_committer(const git_reflog_entry *entry) { assert(entry); return entry->committer; } -char * git_reflog_entry_msg(const git_reflog_entry *entry)+const char * git_reflog_entry_message(const git_reflog_entry *entry) { assert(entry); return entry->msg;+}++int git_reflog_drop(+ git_reflog *reflog,+ size_t idx,+ int rewrite_previous_entry)+{+ size_t entrycount;+ git_reflog_entry *entry, *previous;++ assert(reflog);++ entrycount = git_reflog_entrycount(reflog);++ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);++ if (entry == NULL)+ return GIT_ENOTFOUND;++ reflog_entry_free(entry);++ if (git_vector_remove(+ &reflog->entries, reflog_inverse_index(idx, entrycount)) < 0)+ return -1;++ if (!rewrite_previous_entry)+ return 0;++ /* No need to rewrite anything when removing the most recent entry */+ if (idx == 0)+ return 0;++ /* Have the latest entry just been dropped? */+ if (entrycount == 1)+ return 0;++ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1);++ /* If the oldest entry has just been removed... */+ if (idx == entrycount - 1) {+ /* ...clear the oid_old member of the "new" oldest entry */+ if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0)+ return -1;++ return 0;+ }++ previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);+ git_oid_cpy(&entry->oid_old, &previous->oid_cur);++ return 0; }
libgit2/src/reflog.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -28,6 +28,7 @@ struct git_reflog { char *ref_name;+ git_repository *owner; git_vector entries; };
libgit2/src/refs.c view
@@ -1,1707 +1,965 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#include "refs.h"-#include "hash.h"-#include "repository.h"-#include "fileops.h"-#include "pack.h"-#include "reflog.h"--#include <git2/tag.h>-#include <git2/object.h>--GIT__USE_STRMAP;--#define DEFAULT_NESTING_LEVEL 5-#define MAX_NESTING_LEVEL 10--enum {- GIT_PACKREF_HAS_PEEL = 1,- GIT_PACKREF_WAS_LOOSE = 2-};--struct packref {- git_oid oid;- git_oid peel;- char flags;- char name[GIT_FLEX_ARRAY];-};--static int reference_read(- git_buf *file_content,- time_t *mtime,- const char *repo_path,- const char *ref_name,- int *updated);--/* loose refs */-static int loose_parse_symbolic(git_reference *ref, git_buf *file_content);-static int loose_parse_oid(git_oid *ref, git_buf *file_content);-static int loose_lookup(git_reference *ref);-static int loose_lookup_to_packfile(struct packref **ref_out,- git_repository *repo, const char *name);-static int loose_write(git_reference *ref);--/* packed refs */-static int packed_parse_peel(struct packref *tag_ref,- const char **buffer_out, const char *buffer_end);-static int packed_parse_oid(struct packref **ref_out,- const char **buffer_out, const char *buffer_end);-static int packed_load(git_repository *repo);-static int packed_loadloose(git_repository *repository);-static int packed_write_ref(struct packref *ref, git_filebuf *file);-static int packed_find_peel(git_repository *repo, struct packref *ref);-static int packed_remove_loose(git_repository *repo, git_vector *packing_list);-static int packed_sort(const void *a, const void *b);-static int packed_lookup(git_reference *ref);-static int packed_write(git_repository *repo);--/* internal helpers */-static int reference_path_available(git_repository *repo,- const char *ref, const char *old_ref);-static int reference_delete(git_reference *ref);-static int reference_lookup(git_reference *ref);--/* name normalization */-static int normalize_name(char *buffer_out, size_t out_size,- const char *name, int is_oid_ref);---void git_reference_free(git_reference *reference)-{- if (reference == NULL)- return;-- git__free(reference->name);- reference->name = NULL;-- if (reference->flags & GIT_REF_SYMBOLIC) {- git__free(reference->target.symbolic);- reference->target.symbolic = NULL;- }-- git__free(reference);-}--static int reference_alloc(- git_reference **ref_out,- git_repository *repo,- const char *name)-{- git_reference *reference = NULL;-- assert(ref_out && repo && name);-- reference = git__malloc(sizeof(git_reference));- GITERR_CHECK_ALLOC(reference);-- memset(reference, 0x0, sizeof(git_reference));- reference->owner = repo;-- reference->name = git__strdup(name);- GITERR_CHECK_ALLOC(reference->name);-- *ref_out = reference;- return 0;-}--static int reference_read(- git_buf *file_content,- time_t *mtime,- const char *repo_path,- const char *ref_name,- int *updated)-{- git_buf path = GIT_BUF_INIT;- int result;-- assert(file_content && repo_path && ref_name);-- /* Determine the full path of the file */- if (git_buf_joinpath(&path, repo_path, ref_name) < 0)- return -1;-- result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated);- git_buf_free(&path);- return result;-}--static int loose_parse_symbolic(git_reference *ref, git_buf *file_content)-{- const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF);- const char *refname_start;- char *eol;-- refname_start = (const char *)file_content->ptr;-- if (git_buf_len(file_content) < header_len + 1)- goto corrupt;-- /*- * Assume we have already checked for the header- * before calling this function- */- refname_start += header_len;-- ref->target.symbolic = git__strdup(refname_start);- GITERR_CHECK_ALLOC(ref->target.symbolic);-- /* remove newline at the end of file */- eol = strchr(ref->target.symbolic, '\n');- if (eol == NULL)- goto corrupt;-- *eol = '\0';- if (eol[-1] == '\r')- eol[-1] = '\0';-- return 0;--corrupt:- giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");- return -1;-}--static int loose_parse_oid(git_oid *oid, git_buf *file_content)-{- char *buffer;-- buffer = (char *)file_content->ptr;-- /* File format: 40 chars (OID) + newline */- if (git_buf_len(file_content) < GIT_OID_HEXSZ + 1)- goto corrupt;-- if (git_oid_fromstr(oid, buffer) < 0)- goto corrupt;-- buffer = buffer + GIT_OID_HEXSZ;- if (*buffer == '\r')- buffer++;-- if (*buffer != '\n')- goto corrupt;-- return 0;--corrupt:- giterr_set(GITERR_REFERENCE, "Corrupted loose reference file");- return -1;-}--static git_ref_t loose_guess_rtype(const git_buf *full_path)-{- git_buf ref_file = GIT_BUF_INIT;- git_ref_t type;-- type = GIT_REF_INVALID;-- if (git_futils_readbuffer(&ref_file, full_path->ptr) == 0) {- if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0)- type = GIT_REF_SYMBOLIC;- else- type = GIT_REF_OID;- }-- git_buf_free(&ref_file);- return type;-}--static int loose_lookup(git_reference *ref)-{- int result, updated;- git_buf ref_file = GIT_BUF_INIT;-- result = reference_read(&ref_file, &ref->mtime,- ref->owner->path_repository, ref->name, &updated);-- if (result < 0)- return result;-- if (!updated)- return 0;-- if (ref->flags & GIT_REF_SYMBOLIC) {- git__free(ref->target.symbolic);- ref->target.symbolic = NULL;- }-- ref->flags = 0;-- if (git__prefixcmp((const char *)(ref_file.ptr), GIT_SYMREF) == 0) {- ref->flags |= GIT_REF_SYMBOLIC;- result = loose_parse_symbolic(ref, &ref_file);- } else {- ref->flags |= GIT_REF_OID;- result = loose_parse_oid(&ref->target.oid, &ref_file);- }-- git_buf_free(&ref_file);- return result;-}--static int loose_lookup_to_packfile(- struct packref **ref_out,- git_repository *repo,- const char *name)-{- git_buf ref_file = GIT_BUF_INIT;- struct packref *ref = NULL;- size_t name_len;-- *ref_out = NULL;-- if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0)- return -1;-- name_len = strlen(name);- ref = git__malloc(sizeof(struct packref) + name_len + 1);- GITERR_CHECK_ALLOC(ref);-- memcpy(ref->name, name, name_len);- ref->name[name_len] = 0;-- if (loose_parse_oid(&ref->oid, &ref_file) < 0) {- git_buf_free(&ref_file);- git__free(ref);- return -1;- }-- ref->flags = GIT_PACKREF_WAS_LOOSE;-- *ref_out = ref;- git_buf_free(&ref_file);- return 0;-}--static int loose_write(git_reference *ref)-{- git_filebuf file = GIT_FILEBUF_INIT;- git_buf ref_path = GIT_BUF_INIT;- struct stat st;-- if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)- return -1;-- /* Remove a possibly existing empty directory hierarchy - * which name would collide with the reference name- */- if (git_path_isdir(git_buf_cstr(&ref_path)) && - (git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) {- git_buf_free(&ref_path);- return -1;- }-- if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {- git_buf_free(&ref_path);- return -1;- }-- git_buf_free(&ref_path);-- if (ref->flags & GIT_REF_OID) {- char oid[GIT_OID_HEXSZ + 1];-- git_oid_fmt(oid, &ref->target.oid);- oid[GIT_OID_HEXSZ] = '\0';-- git_filebuf_printf(&file, "%s\n", oid);-- } else if (ref->flags & GIT_REF_SYMBOLIC) {- git_filebuf_printf(&file, GIT_SYMREF "%s\n", ref->target.symbolic);- } else {- assert(0); /* don't let this happen */- }-- if (p_stat(ref_path.ptr, &st) == 0)- ref->mtime = st.st_mtime;-- return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);-}--static int packed_parse_peel(- struct packref *tag_ref,- const char **buffer_out,- const char *buffer_end)-{- const char *buffer = *buffer_out + 1;-- assert(buffer[-1] == '^');-- /* Ensure it's not the first entry of the file */- if (tag_ref == NULL)- goto corrupt;-- /* Ensure reference is a tag */- if (git__prefixcmp(tag_ref->name, GIT_REFS_TAGS_DIR) != 0)- goto corrupt;-- if (buffer + GIT_OID_HEXSZ >= buffer_end)- goto corrupt;-- /* Is this a valid object id? */- if (git_oid_fromstr(&tag_ref->peel, buffer) < 0)- goto corrupt;-- buffer = buffer + GIT_OID_HEXSZ;- if (*buffer == '\r')- buffer++;-- if (*buffer != '\n')- goto corrupt;-- *buffer_out = buffer + 1;- return 0;--corrupt:- giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");- return -1;-}--static int packed_parse_oid(- struct packref **ref_out,- const char **buffer_out,- const char *buffer_end)-{- struct packref *ref = NULL;-- const char *buffer = *buffer_out;- const char *refname_begin, *refname_end;-- size_t refname_len;- git_oid id;-- refname_begin = (buffer + GIT_OID_HEXSZ + 1);- if (refname_begin >= buffer_end || refname_begin[-1] != ' ')- goto corrupt;-- /* Is this a valid object id? */- if (git_oid_fromstr(&id, buffer) < 0)- goto corrupt;-- refname_end = memchr(refname_begin, '\n', buffer_end - refname_begin);- if (refname_end == NULL)- goto corrupt;-- if (refname_end[-1] == '\r')- refname_end--;-- refname_len = refname_end - refname_begin;-- ref = git__malloc(sizeof(struct packref) + refname_len + 1);- GITERR_CHECK_ALLOC(ref);-- memcpy(ref->name, refname_begin, refname_len);- ref->name[refname_len] = 0;-- git_oid_cpy(&ref->oid, &id);-- ref->flags = 0;-- *ref_out = ref;- *buffer_out = refname_end + 1;-- return 0;--corrupt:- git__free(ref);- giterr_set(GITERR_REFERENCE, "The packed references file is corrupted");- return -1;-}--static int packed_load(git_repository *repo)-{- int result, updated;- git_buf packfile = GIT_BUF_INIT;- const char *buffer_start, *buffer_end;- git_refcache *ref_cache = &repo->references;-- /* First we make sure we have allocated the hash table */- if (ref_cache->packfile == NULL) {- ref_cache->packfile = git_strmap_alloc();- GITERR_CHECK_ALLOC(ref_cache->packfile);- }-- result = reference_read(&packfile, &ref_cache->packfile_time,- repo->path_repository, GIT_PACKEDREFS_FILE, &updated);-- /*- * If we couldn't find the file, we need to clear the table and- * return. On any other error, we return that error. If everything- * went fine and the file wasn't updated, then there's nothing new- * for us here, so just return. Anything else means we need to- * refresh the packed refs.- */- if (result == GIT_ENOTFOUND) {- git_strmap_clear(ref_cache->packfile);- return 0;- }-- if (result < 0)- return -1;-- if (!updated)- return 0;-- /*- * At this point, we want to refresh the packed refs. We already- * have the contents in our buffer.- */- git_strmap_clear(ref_cache->packfile);-- buffer_start = (const char *)packfile.ptr;- buffer_end = (const char *)(buffer_start) + packfile.size;-- while (buffer_start < buffer_end && buffer_start[0] == '#') {- buffer_start = strchr(buffer_start, '\n');- if (buffer_start == NULL)- goto parse_failed;-- buffer_start++;- }-- while (buffer_start < buffer_end) {- int err;- struct packref *ref = NULL;-- if (packed_parse_oid(&ref, &buffer_start, buffer_end) < 0)- goto parse_failed;-- if (buffer_start[0] == '^') {- if (packed_parse_peel(ref, &buffer_start, buffer_end) < 0)- goto parse_failed;- }-- git_strmap_insert(ref_cache->packfile, ref->name, ref, err);- if (err < 0)- goto parse_failed;- }-- git_buf_free(&packfile);- return 0;--parse_failed:- git_strmap_free(ref_cache->packfile);- ref_cache->packfile = NULL;- git_buf_free(&packfile);- return -1;-}---struct dirent_list_data {- git_repository *repo;- size_t repo_path_len;- unsigned int list_flags;-- int (*callback)(const char *, void *);- void *callback_payload;-};--static int _dirent_loose_listall(void *_data, git_buf *full_path)-{- struct dirent_list_data *data = (struct dirent_list_data *)_data;- const char *file_path = full_path->ptr + data->repo_path_len;-- if (git_path_isdir(full_path->ptr) == true)- return git_path_direach(full_path, _dirent_loose_listall, _data);-- /* do not add twice a reference that exists already in the packfile */- if ((data->list_flags & GIT_REF_PACKED) != 0 &&- git_strmap_exists(data->repo->references.packfile, file_path))- return 0;-- if (data->list_flags != GIT_REF_LISTALL) {- if ((data->list_flags & loose_guess_rtype(full_path)) == 0)- return 0; /* we are filtering out this reference */- }-- return data->callback(file_path, data->callback_payload);-}--static int _dirent_loose_load(void *data, git_buf *full_path)-{- git_repository *repository = (git_repository *)data;- void *old_ref = NULL;- struct packref *ref;- const char *file_path;- int err;-- if (git_path_isdir(full_path->ptr) == true)- return git_path_direach(full_path, _dirent_loose_load, repository);-- file_path = full_path->ptr + strlen(repository->path_repository);-- if (loose_lookup_to_packfile(&ref, repository, file_path) < 0)- return -1;-- git_strmap_insert2(- repository->references.packfile, ref->name, ref, old_ref, err);- if (err < 0) {- git__free(ref);- return -1;- }-- git__free(old_ref);- return 0;-}--/*- * Load all the loose references from the repository- * into the in-memory Packfile, and build a vector with- * all the references so it can be written back to- * disk.- */-static int packed_loadloose(git_repository *repository)-{- git_buf refs_path = GIT_BUF_INIT;- int result;-- /* the packfile must have been previously loaded! */- assert(repository->references.packfile);-- if (git_buf_joinpath(&refs_path, repository->path_repository, GIT_REFS_DIR) < 0)- return -1;-- /*- * Load all the loose files from disk into the Packfile table.- * This will overwrite any old packed entries with their- * updated loose versions- */- result = git_path_direach(&refs_path, _dirent_loose_load, repository);- git_buf_free(&refs_path);-- return result;-}--/*- * Write a single reference into a packfile- */-static int packed_write_ref(struct packref *ref, git_filebuf *file)-{- char oid[GIT_OID_HEXSZ + 1];-- git_oid_fmt(oid, &ref->oid);- oid[GIT_OID_HEXSZ] = 0;-- /*- * For references that peel to an object in the repo, we must- * write the resulting peel on a separate line, e.g.- *- * 6fa8a902cc1d18527e1355773c86721945475d37 refs/tags/libgit2-0.4- * ^2ec0cb7959b0bf965d54f95453f5b4b34e8d3100- *- * This obviously only applies to tags.- * The required peels have already been loaded into `ref->peel_target`.- */- if (ref->flags & GIT_PACKREF_HAS_PEEL) {- char peel[GIT_OID_HEXSZ + 1];- git_oid_fmt(peel, &ref->peel);- peel[GIT_OID_HEXSZ] = 0;-- if (git_filebuf_printf(file, "%s %s\n^%s\n", oid, ref->name, peel) < 0)- return -1;- } else {- if (git_filebuf_printf(file, "%s %s\n", oid, ref->name) < 0)- return -1;- }-- return 0;-}--/*- * Find out what object this reference resolves to.- *- * For references that point to a 'big' tag (e.g. an- * actual tag object on the repository), we need to- * cache on the packfile the OID of the object to- * which that 'big tag' is pointing to.- */-static int packed_find_peel(git_repository *repo, struct packref *ref)-{- git_object *object;-- if (ref->flags & GIT_PACKREF_HAS_PEEL)- return 0;-- /*- * Only applies to tags, i.e. references- * in the /refs/tags folder- */- if (git__prefixcmp(ref->name, GIT_REFS_TAGS_DIR) != 0)- return 0;-- /*- * Find the tagged object in the repository- */- if (git_object_lookup(&object, repo, &ref->oid, GIT_OBJ_ANY) < 0)- return -1;-- /*- * If the tagged object is a Tag object, we need to resolve it;- * if the ref is actually a 'weak' ref, we don't need to resolve- * anything.- */- if (git_object_type(object) == GIT_OBJ_TAG) {- git_tag *tag = (git_tag *)object;-- /*- * Find the object pointed at by this tag- */- git_oid_cpy(&ref->peel, git_tag_target_oid(tag));- ref->flags |= GIT_PACKREF_HAS_PEEL;-- /*- * The reference has now cached the resolved OID, and is- * marked at such. When written to the packfile, it'll be- * accompanied by this resolved oid- */- }-- git_object_free(object);- return 0;-}--/*- * Remove all loose references- *- * Once we have successfully written a packfile,- * all the loose references that were packed must be- * removed from disk.- *- * This is a dangerous method; make sure the packfile- * is well-written, because we are destructing references- * here otherwise.- */-static int packed_remove_loose(git_repository *repo, git_vector *packing_list)-{- unsigned int i;- git_buf full_path = GIT_BUF_INIT;- int failed = 0;-- for (i = 0; i < packing_list->length; ++i) {- struct packref *ref = git_vector_get(packing_list, i);-- if ((ref->flags & GIT_PACKREF_WAS_LOOSE) == 0)- continue;-- if (git_buf_joinpath(&full_path, repo->path_repository, ref->name) < 0)- return -1; /* critical; do not try to recover on oom */-- if (git_path_exists(full_path.ptr) == true && p_unlink(full_path.ptr) < 0) {- if (failed)- continue;-- giterr_set(GITERR_REFERENCE,- "Failed to remove loose reference '%s' after packing: %s",- full_path.ptr, strerror(errno));-- failed = 1;- }-- /*- * if we fail to remove a single file, this is *not* good,- * but we should keep going and remove as many as possible.- * After we've removed as many files as possible, we return- * the error code anyway.- */- }-- git_buf_free(&full_path);- return failed ? -1 : 0;-}--static int packed_sort(const void *a, const void *b)-{- const struct packref *ref_a = (const struct packref *)a;- const struct packref *ref_b = (const struct packref *)b;-- return strcmp(ref_a->name, ref_b->name);-}--/*- * Write all the contents in the in-memory packfile to disk.- */-static int packed_write(git_repository *repo)-{- git_filebuf pack_file = GIT_FILEBUF_INIT;- unsigned int i;- git_buf pack_file_path = GIT_BUF_INIT;- git_vector packing_list;- unsigned int total_refs;-- assert(repo && repo->references.packfile);-- total_refs =- (unsigned int)git_strmap_num_entries(repo->references.packfile);-- if (git_vector_init(&packing_list, total_refs, packed_sort) < 0)- return -1;-- /* Load all the packfile into a vector */- {- struct packref *reference;-- /* cannot fail: vector already has the right size */- git_strmap_foreach_value(repo->references.packfile, reference, {- git_vector_insert(&packing_list, reference);- });- }-- /* sort the vector so the entries appear sorted on the packfile */- git_vector_sort(&packing_list);-- /* Now we can open the file! */- if (git_buf_joinpath(&pack_file_path, repo->path_repository, GIT_PACKEDREFS_FILE) < 0)- goto cleanup_memory;-- if (git_filebuf_open(&pack_file, pack_file_path.ptr, 0) < 0)- goto cleanup_packfile;-- /* Packfiles have a header... apparently- * This is in fact not required, but we might as well print it- * just for kicks */- if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0)- goto cleanup_packfile;-- for (i = 0; i < packing_list.length; ++i) {- struct packref *ref = (struct packref *)git_vector_get(&packing_list, i);-- if (packed_find_peel(repo, ref) < 0)- goto cleanup_packfile;-- if (packed_write_ref(ref, &pack_file) < 0)- goto cleanup_packfile;- }-- /* if we've written all the references properly, we can commit- * the packfile to make the changes effective */- if (git_filebuf_commit(&pack_file, GIT_PACKEDREFS_FILE_MODE) < 0)- goto cleanup_memory;-- /* when and only when the packfile has been properly written,- * we can go ahead and remove the loose refs */- if (packed_remove_loose(repo, &packing_list) < 0)- goto cleanup_memory;-- {- struct stat st;- if (p_stat(pack_file_path.ptr, &st) == 0)- repo->references.packfile_time = st.st_mtime;- }-- git_vector_free(&packing_list);- git_buf_free(&pack_file_path);-- /* we're good now */- return 0;--cleanup_packfile:- git_filebuf_cleanup(&pack_file);--cleanup_memory:- git_vector_free(&packing_list);- git_buf_free(&pack_file_path);-- return -1;-}--struct reference_available_t {- const char *new_ref;- const char *old_ref;- int available;-};--static int _reference_available_cb(const char *ref, void *data)-{- struct reference_available_t *d;-- assert(ref && data);- d = (struct reference_available_t *)data;-- if (!d->old_ref || strcmp(d->old_ref, ref)) {- size_t reflen = strlen(ref);- size_t newlen = strlen(d->new_ref);- size_t cmplen = reflen < newlen ? reflen : newlen;- const char *lead = reflen < newlen ? d->new_ref : ref;-- if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') {- d->available = 0;- return -1;- }- }-- return 0;-}--static int reference_path_available(- git_repository *repo,- const char *ref,- const char* old_ref)-{- struct reference_available_t data;-- data.new_ref = ref;- data.old_ref = old_ref;- data.available = 1;-- if (git_reference_foreach(repo, GIT_REF_LISTALL,- _reference_available_cb, (void *)&data) < 0)- return -1;-- if (!data.available) {- giterr_set(GITERR_REFERENCE,- "The path to reference '%s' collides with an existing one", ref);- return -1;- }-- return 0;-}--static int reference_exists(int *exists, git_repository *repo, const char *ref_name)-{- git_buf ref_path = GIT_BUF_INIT;-- if (packed_load(repo) < 0)- return -1;-- if (git_buf_joinpath(&ref_path, repo->path_repository, ref_name) < 0)- return -1;-- if (git_path_isfile(ref_path.ptr) == true ||- git_strmap_exists(repo->references.packfile, ref_path.ptr))- {- *exists = 1;- } else {- *exists = 0;- }-- git_buf_free(&ref_path);- return 0;-}--/*- * Check if a reference could be written to disk, based on:- *- * - Whether a reference with the same name already exists,- * and we are allowing or disallowing overwrites- *- * - Whether the name of the reference would collide with- * an existing path- */-static int reference_can_write(- git_repository *repo,- const char *refname,- const char *previous_name,- int force)-{- /* see if the reference shares a path with an existing reference;- * if a path is shared, we cannot create the reference, even when forcing */- if (reference_path_available(repo, refname, previous_name) < 0)- return -1;-- /* check if the reference actually exists, but only if we are not forcing- * the rename. If we are forcing, it's OK to overwrite */- if (!force) {- int exists;-- if (reference_exists(&exists, repo, refname) < 0)- return -1;-- /* We cannot proceed if the reference already exists and we're not forcing- * the rename; the existing one would be overwritten */- if (exists) {- giterr_set(GITERR_REFERENCE,- "A reference with that name (%s) already exists", refname);- return GIT_EEXISTS;- }- }-- /* FIXME: if the reference exists and we are forcing, do we really need to- * remove the reference first?- *- * Two cases:- *- * - the reference already exists and is loose: not a problem, the file- * gets overwritten on disk- *- * - the reference already exists and is packed: we write a new one as- * loose, which by all means renders the packed one useless- */-- return 0;-}---static int packed_lookup(git_reference *ref)-{- struct packref *pack_ref = NULL;- git_strmap *packfile_refs;- khiter_t pos;-- if (packed_load(ref->owner) < 0)- return -1;-- /* maybe the packfile hasn't changed at all, so we don't- * have to re-lookup the reference */- if ((ref->flags & GIT_REF_PACKED) &&- ref->mtime == ref->owner->references.packfile_time)- return 0;-- if (ref->flags & GIT_REF_SYMBOLIC) {- git__free(ref->target.symbolic);- ref->target.symbolic = NULL;- }-- /* Look up on the packfile */- packfile_refs = ref->owner->references.packfile;- pos = git_strmap_lookup_index(packfile_refs, ref->name);- if (!git_strmap_valid_index(packfile_refs, pos)) {- giterr_set(GITERR_REFERENCE, "Reference '%s' not found", ref->name);- return GIT_ENOTFOUND;- }-- pack_ref = git_strmap_value_at(packfile_refs, pos);-- ref->flags = GIT_REF_OID | GIT_REF_PACKED;- ref->mtime = ref->owner->references.packfile_time;- git_oid_cpy(&ref->target.oid, &pack_ref->oid);-- return 0;-}--static int reference_lookup(git_reference *ref)-{- int result;-- result = loose_lookup(ref);- if (result == 0)- return 0;-- /* only try to lookup this reference on the packfile if it- * wasn't found on the loose refs; not if there was a critical error */- if (result == GIT_ENOTFOUND) {- giterr_clear();- result = packed_lookup(ref);- if (result == 0)- return 0;- }-- /* unexpected error; free the reference */- git_reference_free(ref);- return result;-}--/*- * Delete a reference.- * This is an internal method; the reference is removed- * from disk or the packfile, but the pointer is not freed- */-static int reference_delete(git_reference *ref)-{- int result;-- assert(ref);-- /* If the reference is packed, this is an expensive operation.- * We need to reload the packfile, remove the reference from the- * packing list, and repack */- if (ref->flags & GIT_REF_PACKED) {- git_strmap *packfile_refs;- struct packref *packref;- khiter_t pos;-- /* load the existing packfile */- if (packed_load(ref->owner) < 0)- return -1;-- packfile_refs = ref->owner->references.packfile;- pos = git_strmap_lookup_index(packfile_refs, ref->name);- if (!git_strmap_valid_index(packfile_refs, pos)) {- giterr_set(GITERR_REFERENCE,- "Reference %s stopped existing in the packfile", ref->name);- return -1;- }-- packref = git_strmap_value_at(packfile_refs, pos);- git_strmap_delete_at(packfile_refs, pos);-- git__free(packref);- if (packed_write(ref->owner) < 0)- return -1;-- /* If the reference is loose, we can just remove the reference- * from the filesystem */- } else {- git_reference *ref_in_pack;- git_buf full_path = GIT_BUF_INIT;-- if (git_buf_joinpath(&full_path, ref->owner->path_repository, ref->name) < 0)- return -1;-- result = p_unlink(full_path.ptr);- git_buf_free(&full_path); /* done with path at this point */-- if (result < 0) {- giterr_set(GITERR_OS, "Failed to unlink '%s'", full_path.ptr);- return -1;- }-- /* When deleting a loose reference, we have to ensure that an older- * packed version of it doesn't exist */- if (git_reference_lookup(&ref_in_pack, ref->owner, ref->name) == 0) {- assert((ref_in_pack->flags & GIT_REF_PACKED) != 0);- return git_reference_delete(ref_in_pack);- }-- giterr_clear();- }-- return 0;-}--int git_reference_delete(git_reference *ref)-{- int result = reference_delete(ref);- git_reference_free(ref);- return result;-}--int git_reference_lookup(git_reference **ref_out,- git_repository *repo, const char *name)-{- return git_reference_lookup_resolved(ref_out, repo, name, 0);-}--int git_reference_name_to_oid(- git_oid *out, git_repository *repo, const char *name)-{- int error;- git_reference *ref;-- if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)- return error;-- git_oid_cpy(out, git_reference_oid(ref));- git_reference_free(ref);- return 0;-}--int git_reference_lookup_resolved(- git_reference **ref_out,- git_repository *repo,- const char *name,- int max_nesting)-{- git_reference *scan;- int result, nesting;-- assert(ref_out && repo && name);-- *ref_out = NULL;-- if (max_nesting > MAX_NESTING_LEVEL)- max_nesting = MAX_NESTING_LEVEL;- else if (max_nesting < 0)- max_nesting = DEFAULT_NESTING_LEVEL;-- scan = git__calloc(1, sizeof(git_reference));- GITERR_CHECK_ALLOC(scan);-- scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char));- GITERR_CHECK_ALLOC(scan->name);-- if ((result = normalize_name(scan->name, GIT_REFNAME_MAX, name, 0)) < 0) {- git_reference_free(scan);- return result;- }-- scan->target.symbolic = git__strdup(scan->name);- GITERR_CHECK_ALLOC(scan->target.symbolic);-- scan->owner = repo;- scan->flags = GIT_REF_SYMBOLIC;-- for (nesting = max_nesting;- nesting >= 0 && (scan->flags & GIT_REF_SYMBOLIC) != 0;- nesting--)- {- if (nesting != max_nesting)- strncpy(scan->name, scan->target.symbolic, GIT_REFNAME_MAX);-- scan->mtime = 0;-- if ((result = reference_lookup(scan)) < 0)- return result; /* lookup git_reference_free on scan already */- }-- if ((scan->flags & GIT_REF_OID) == 0 && max_nesting != 0) {- giterr_set(GITERR_REFERENCE,- "Cannot resolve reference (>%u levels deep)", max_nesting);- git_reference_free(scan);- return -1;- }-- *ref_out = scan;- return 0;-}--/**- * Getters- */-git_ref_t git_reference_type(git_reference *ref)-{- assert(ref);-- if (ref->flags & GIT_REF_OID)- return GIT_REF_OID;-- if (ref->flags & GIT_REF_SYMBOLIC)- return GIT_REF_SYMBOLIC;-- return GIT_REF_INVALID;-}--int git_reference_is_packed(git_reference *ref)-{- assert(ref);- return !!(ref->flags & GIT_REF_PACKED);-}--const char *git_reference_name(git_reference *ref)-{- assert(ref);- return ref->name;-}--git_repository *git_reference_owner(git_reference *ref)-{- assert(ref);- return ref->owner;-}--const git_oid *git_reference_oid(git_reference *ref)-{- assert(ref);-- if ((ref->flags & GIT_REF_OID) == 0)- return NULL;-- return &ref->target.oid;-}--const char *git_reference_target(git_reference *ref)-{- assert(ref);-- if ((ref->flags & GIT_REF_SYMBOLIC) == 0)- return NULL;-- return ref->target.symbolic;-}--int git_reference_create_symbolic(- git_reference **ref_out,- git_repository *repo,- const char *name,- const char *target,- int force)-{- char normalized[GIT_REFNAME_MAX];- git_reference *ref = NULL;-- if (normalize_name(normalized, sizeof(normalized), name, 0) < 0)- return -1;-- if (reference_can_write(repo, normalized, NULL, force) < 0)- return -1;-- if (reference_alloc(&ref, repo, normalized) < 0)- return -1;-- ref->flags |= GIT_REF_SYMBOLIC;-- /* set the target; this will normalize the name automatically- * and write the reference on disk */- if (git_reference_set_target(ref, target) < 0) {- git_reference_free(ref);- return -1;- }- if (ref_out == NULL) {- git_reference_free(ref);- } else {- *ref_out = ref;- }-- return 0;-}--int git_reference_create_oid(- git_reference **ref_out,- git_repository *repo,- const char *name,- const git_oid *id,- int force)-{- git_reference *ref = NULL;- char normalized[GIT_REFNAME_MAX];-- if (normalize_name(normalized, sizeof(normalized), name, 1) < 0)- return -1;-- if (reference_can_write(repo, normalized, NULL, force) < 0)- return -1;-- if (reference_alloc(&ref, repo, name) < 0)- return -1;-- ref->flags |= GIT_REF_OID;-- /* set the oid; this will write the reference on disk */- if (git_reference_set_oid(ref, id) < 0) {- git_reference_free(ref);- return -1;- }-- if (ref_out == NULL) {- git_reference_free(ref);- } else {- *ref_out = ref;- }-- return 0;-}-/*- * Change the OID target of a reference.- *- * For both loose and packed references, just change- * the oid in memory and (over)write the file in disk.- *- * We do not repack packed references because of performance- * reasons.- */-int git_reference_set_oid(git_reference *ref, const git_oid *id)-{- git_odb *odb = NULL;-- if ((ref->flags & GIT_REF_OID) == 0) {- giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");- return -1;- }-- assert(ref->owner);-- if (git_repository_odb__weakptr(&odb, ref->owner) < 0)- return -1;-- /* Don't let the user create references to OIDs that- * don't exist in the ODB */- if (!git_odb_exists(odb, id)) {- giterr_set(GITERR_REFERENCE,- "Target OID for the reference doesn't exist on the repository");- return -1;- }-- /* Update the OID value on `ref` */- git_oid_cpy(&ref->target.oid, id);-- /* Write back to disk */- return loose_write(ref);-}--/*- * Change the target of a symbolic reference.- *- * This is easy because symrefs cannot be inside- * a pack. We just change the target in memory- * and overwrite the file on disk.- */-int git_reference_set_target(git_reference *ref, const char *target)-{- char normalized[GIT_REFNAME_MAX];-- if ((ref->flags & GIT_REF_SYMBOLIC) == 0) {- giterr_set(GITERR_REFERENCE,- "Cannot set symbolic target on a direct reference");- return -1;- }-- if (normalize_name(normalized, sizeof(normalized), target, 0))- return -1;-- git__free(ref->target.symbolic);- ref->target.symbolic = git__strdup(normalized);- GITERR_CHECK_ALLOC(ref->target.symbolic);-- return loose_write(ref);-}--int git_reference_rename(git_reference *ref, const char *new_name, int force)-{- int result;- git_buf aux_path = GIT_BUF_INIT;- char normalized[GIT_REFNAME_MAX];-- const char *head_target = NULL;- git_reference *head = NULL;-- if (normalize_name(normalized, sizeof(normalized),- new_name, ref->flags & GIT_REF_OID) < 0)- return -1;-- if (reference_can_write(ref->owner, normalized, ref->name, force) < 0)- return -1;-- /* Initialize path now so we won't get an allocation failure once- * we actually start removing things. */- if (git_buf_joinpath(&aux_path, ref->owner->path_repository, new_name) < 0)- return -1;-- /*- * Now delete the old ref and remove an possibly existing directory- * named `new_name`. Note that using the internal `reference_delete`- * method deletes the ref from disk but doesn't free the pointer, so- * we can still access the ref's attributes for creating the new one- */- if (reference_delete(ref) < 0)- goto cleanup;-- /*- * Finally we can create the new reference.- */- if (ref->flags & GIT_REF_SYMBOLIC) {- result = git_reference_create_symbolic(- NULL, ref->owner, new_name, ref->target.symbolic, force);- } else {- result = git_reference_create_oid(- NULL, ref->owner, new_name, &ref->target.oid, force);- }-- if (result < 0)- goto rollback;-- /*- * Check if we have to update HEAD.- */- if (git_reference_lookup(&head, ref->owner, GIT_HEAD_FILE) < 0) {- giterr_set(GITERR_REFERENCE,- "Failed to update HEAD after renaming reference");- goto cleanup;- }-- head_target = git_reference_target(head);-- if (head_target && !strcmp(head_target, ref->name)) {- if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) {- giterr_set(GITERR_REFERENCE,- "Failed to update HEAD after renaming reference");- goto cleanup;- }- }-- /*- * Rename the reflog file.- */- if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0)- goto cleanup;-- if (git_path_exists(aux_path.ptr) == true) {- if (git_reflog_rename(ref, new_name) < 0)- goto cleanup;- } else {- giterr_clear();- }-- /*- * Change the name of the reference given by the user.- */- git__free(ref->name);- ref->name = git__strdup(new_name);-- /* The reference is no longer packed */- ref->flags &= ~GIT_REF_PACKED;-- git_reference_free(head);- git_buf_free(&aux_path);- return 0;--cleanup:- git_reference_free(head);- git_buf_free(&aux_path);- return -1;--rollback:- /*- * Try to create the old reference again, ignore failures- */- if (ref->flags & GIT_REF_SYMBOLIC)- git_reference_create_symbolic(- NULL, ref->owner, ref->name, ref->target.symbolic, 0);- else- git_reference_create_oid(- NULL, ref->owner, ref->name, &ref->target.oid, 0);-- /* The reference is no longer packed */- ref->flags &= ~GIT_REF_PACKED;-- git_buf_free(&aux_path);- return -1;-}--int git_reference_resolve(git_reference **ref_out, git_reference *ref)-{- if (ref->flags & GIT_REF_OID)- return git_reference_lookup(ref_out, ref->owner, ref->name);- else- return git_reference_lookup_resolved(ref_out, ref->owner, ref->target.symbolic, -1);-}--int git_reference_packall(git_repository *repo)-{- if (packed_load(repo) < 0 || /* load the existing packfile */- packed_loadloose(repo) < 0 || /* add all the loose refs */- packed_write(repo) < 0) /* write back to disk */- return -1;-- return 0;-}--int git_reference_foreach(- git_repository *repo,- unsigned int list_flags,- int (*callback)(const char *, void *),- void *payload)-{- int result;- struct dirent_list_data data;- git_buf refs_path = GIT_BUF_INIT;-- /* list all the packed references first */- if (list_flags & GIT_REF_PACKED) {- const char *ref_name;- void *ref;- GIT_UNUSED(ref);-- if (packed_load(repo) < 0)- return -1;-- git_strmap_foreach(repo->references.packfile, ref_name, ref, {- if (callback(ref_name, payload) < 0)- return 0;- });- }-- /* now list the loose references, trying not to- * duplicate the ref names already in the packed-refs file */-- data.repo_path_len = strlen(repo->path_repository);- data.list_flags = list_flags;- data.repo = repo;- data.callback = callback;- data.callback_payload = payload;-- if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0)- return -1;-- result = git_path_direach(&refs_path, _dirent_loose_listall, &data);- git_buf_free(&refs_path);-- return result;-}--static int cb__reflist_add(const char *ref, void *data)-{- return git_vector_insert((git_vector *)data, git__strdup(ref));-}--int git_reference_list(- git_strarray *array,- git_repository *repo,- unsigned int list_flags)-{- git_vector ref_list;-- assert(array && repo);-- array->strings = NULL;- array->count = 0;-- if (git_vector_init(&ref_list, 8, NULL) < 0)- return -1;-- if (git_reference_foreach(- repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) {- git_vector_free(&ref_list);- return -1;- }-- array->strings = (char **)ref_list.contents;- array->count = ref_list.length;- return 0;-}--int git_reference_reload(git_reference *ref)-{- return reference_lookup(ref);-}--void git_repository__refcache_free(git_refcache *refs)-{- assert(refs);-- if (refs->packfile) {- struct packref *reference;-- git_strmap_foreach_value(refs->packfile, reference, {- git__free(reference);- });-- git_strmap_free(refs->packfile);- }-}--static int is_valid_ref_char(char ch)-{- if ((unsigned) ch <= ' ')- return 0;-- switch (ch) {- case '~':- case '^':- case ':':- case '\\':- case '?':- case '[':- case '*':- return 0;- default:- return 1;- }-}--static int normalize_name(- char *buffer_out,- size_t out_size,- const char *name,- int is_oid_ref)-{- const char *name_end, *buffer_out_start;- const char *current;- int contains_a_slash = 0;-- assert(name && buffer_out);-- buffer_out_start = buffer_out;- current = name;- name_end = name + strlen(name);-- /* Terminating null byte */- out_size--;-- /* A refname can not be empty */- if (name_end == name)- goto invalid_name;-- /* A refname can not end with a dot or a slash */- if (*(name_end - 1) == '.' || *(name_end - 1) == '/')- goto invalid_name;-- while (current < name_end && out_size) {- if (!is_valid_ref_char(*current))- goto invalid_name;-- if (buffer_out > buffer_out_start) {- char prev = *(buffer_out - 1);-- /* A refname can not start with a dot nor contain a double dot */- if (*current == '.' && ((prev == '.') || (prev == '/')))- goto invalid_name;-- /* '@{' is forbidden within a refname */- if (*current == '{' && prev == '@')- goto invalid_name;-- /* Prevent multiple slashes from being added to the output */- if (*current == '/' && prev == '/') {- current++;- continue;- }- }-- if (*current == '/')- contains_a_slash = 1;-- *buffer_out++ = *current++;- out_size--;- }-- if (!out_size)- goto invalid_name;-- /* Object id refname have to contain at least one slash, except- * for HEAD in a detached state or MERGE_HEAD if we're in the- * middle of a merge */- if (is_oid_ref &&- !contains_a_slash &&- strcmp(name, GIT_HEAD_FILE) != 0 &&- strcmp(name, GIT_MERGE_HEAD_FILE) != 0 &&- strcmp(name, GIT_FETCH_HEAD_FILE) != 0)- goto invalid_name;-- /* A refname can not end with ".lock" */- if (!git__suffixcmp(name, GIT_FILELOCK_EXTENSION))- goto invalid_name;-- *buffer_out = '\0';-- /*- * For object id references, name has to start with refs/. Again,- * we need to allow HEAD to be in a detached state.- */- if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) ||- strcmp(buffer_out_start, GIT_HEAD_FILE)))- goto invalid_name;-- return 0;--invalid_name:- giterr_set(GITERR_REFERENCE, "The given reference name is not valid");- return -1;-}--int git_reference__normalize_name(- char *buffer_out,- size_t out_size,- const char *name)-{- return normalize_name(buffer_out, out_size, name, 0);-}--int git_reference__normalize_name_oid(- char *buffer_out,- size_t out_size,- const char *name)-{- return normalize_name(buffer_out, out_size, name, 1);-}--#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)--int git_reference_cmp(git_reference *ref1, git_reference *ref2)-{- assert(ref1 && ref2);-- /* let's put symbolic refs before OIDs */- if ((ref1->flags & GIT_REF_TYPEMASK) != (ref2->flags & GIT_REF_TYPEMASK))- return (ref1->flags & GIT_REF_SYMBOLIC) ? -1 : 1;-- if (ref1->flags & GIT_REF_SYMBOLIC)- return strcmp(ref1->target.symbolic, ref2->target.symbolic);-- return git_oid_cmp(&ref1->target.oid, &ref2->target.oid);-}-+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "refs.h"+#include "hash.h"+#include "repository.h"+#include "fileops.h"+#include "pack.h"+#include "reflog.h"+#include "refdb.h"++#include <git2/tag.h>+#include <git2/object.h>+#include <git2/oid.h>+#include <git2/branch.h>+#include <git2/refs.h>+#include <git2/refdb.h>+#include <git2/refdb_backend.h>++GIT__USE_STRMAP;++#define DEFAULT_NESTING_LEVEL 5+#define MAX_NESTING_LEVEL 10++enum {+ GIT_PACKREF_HAS_PEEL = 1,+ GIT_PACKREF_WAS_LOOSE = 2+};+++git_reference *git_reference__alloc(+ git_refdb *refdb,+ const char *name,+ const git_oid *oid,+ const char *symbolic)+{+ git_reference *ref;+ size_t namelen;++ assert(refdb && name && ((oid && !symbolic) || (!oid && symbolic)));++ namelen = strlen(name);++ if ((ref = git__calloc(1, sizeof(git_reference) + namelen + 1)) == NULL)+ return NULL;++ if (oid) {+ ref->type = GIT_REF_OID;+ git_oid_cpy(&ref->target.oid, oid);+ } else {+ ref->type = GIT_REF_SYMBOLIC;++ if ((ref->target.symbolic = git__strdup(symbolic)) == NULL) {+ git__free(ref);+ return NULL;+ }+ }++ ref->db = refdb;+ memcpy(ref->name, name, namelen + 1);++ return ref;+}++void git_reference_free(git_reference *reference)+{+ if (reference == NULL)+ return;++ if (reference->type == GIT_REF_SYMBOLIC) {+ git__free(reference->target.symbolic);+ reference->target.symbolic = NULL;+ }++ reference->db = NULL;+ reference->type = GIT_REF_INVALID;++ git__free(reference);+}++struct reference_available_t {+ const char *new_ref;+ const char *old_ref;+ int available;+};++static int _reference_available_cb(const char *ref, void *data)+{+ struct reference_available_t *d;++ assert(ref && data);+ d = (struct reference_available_t *)data;++ if (!d->old_ref || strcmp(d->old_ref, ref)) {+ size_t reflen = strlen(ref);+ size_t newlen = strlen(d->new_ref);+ size_t cmplen = reflen < newlen ? reflen : newlen;+ const char *lead = reflen < newlen ? d->new_ref : ref;++ if (!strncmp(d->new_ref, ref, cmplen) && lead[cmplen] == '/') {+ d->available = 0;+ return -1;+ }+ }++ return 0;+}++static int reference_path_available(+ git_repository *repo,+ const char *ref,+ const char* old_ref)+{+ int error;+ struct reference_available_t data;++ data.new_ref = ref;+ data.old_ref = old_ref;+ data.available = 1;++ error = git_reference_foreach(+ repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&data);+ if (error < 0)+ return error;++ if (!data.available) {+ giterr_set(GITERR_REFERENCE,+ "The path to reference '%s' collides with an existing one", ref);+ return -1;+ }++ return 0;+}++/*+ * Check if a reference could be written to disk, based on:+ *+ * - Whether a reference with the same name already exists,+ * and we are allowing or disallowing overwrites+ *+ * - Whether the name of the reference would collide with+ * an existing path+ */+static int reference_can_write(+ git_repository *repo,+ const char *refname,+ const char *previous_name,+ int force)+{+ git_refdb *refdb;++ if (git_repository_refdb__weakptr(&refdb, repo) < 0)+ return -1;++ /* see if the reference shares a path with an existing reference;+ * if a path is shared, we cannot create the reference, even when forcing */+ if (reference_path_available(repo, refname, previous_name) < 0)+ return -1;++ /* check if the reference actually exists, but only if we are not forcing+ * the rename. If we are forcing, it's OK to overwrite */+ if (!force) {+ int exists;++ if (git_refdb_exists(&exists, refdb, refname) < 0)+ return -1;++ /* We cannot proceed if the reference already exists and we're not forcing+ * the rename; the existing one would be overwritten */+ if (exists) {+ giterr_set(GITERR_REFERENCE,+ "A reference with that name (%s) already exists", refname);+ return GIT_EEXISTS;+ }+ }++ /* FIXME: if the reference exists and we are forcing, do we really need to+ * remove the reference first?+ *+ * Two cases:+ *+ * - the reference already exists and is loose: not a problem, the file+ * gets overwritten on disk+ *+ * - the reference already exists and is packed: we write a new one as+ * loose, which by all means renders the packed one useless+ */++ return 0;+}++int git_reference_delete(git_reference *ref)+{+ return git_refdb_delete(ref->db, ref);+}++int git_reference_lookup(git_reference **ref_out,+ git_repository *repo, const char *name)+{+ return git_reference_lookup_resolved(ref_out, repo, name, 0);+}++int git_reference_name_to_id(+ git_oid *out, git_repository *repo, const char *name)+{+ int error;+ git_reference *ref;++ if ((error = git_reference_lookup_resolved(&ref, repo, name, -1)) < 0)+ return error;++ git_oid_cpy(out, git_reference_target(ref));+ git_reference_free(ref);+ return 0;+}++int git_reference_lookup_resolved(+ git_reference **ref_out,+ git_repository *repo,+ const char *name,+ int max_nesting)+{+ char scan_name[GIT_REFNAME_MAX];+ git_ref_t scan_type;+ int error = 0, nesting;+ git_reference *ref = NULL;+ git_refdb *refdb;++ assert(ref_out && repo && name);++ *ref_out = NULL;++ if (max_nesting > MAX_NESTING_LEVEL)+ max_nesting = MAX_NESTING_LEVEL;+ else if (max_nesting < 0)+ max_nesting = DEFAULT_NESTING_LEVEL;+ + strncpy(scan_name, name, GIT_REFNAME_MAX);+ scan_type = GIT_REF_SYMBOLIC;+ + if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0)+ return -1;++ if ((error = git_reference__normalize_name_lax(scan_name, GIT_REFNAME_MAX, name)) < 0)+ return error;++ for (nesting = max_nesting;+ nesting >= 0 && scan_type == GIT_REF_SYMBOLIC;+ nesting--)+ {+ if (nesting != max_nesting) {+ strncpy(scan_name, ref->target.symbolic, GIT_REFNAME_MAX);+ git_reference_free(ref);+ }++ if ((error = git_refdb_lookup(&ref, refdb, scan_name)) < 0)+ return error;+ + scan_type = ref->type;+ }++ if (scan_type != GIT_REF_OID && max_nesting != 0) {+ giterr_set(GITERR_REFERENCE,+ "Cannot resolve reference (>%u levels deep)", max_nesting);+ git_reference_free(ref);+ return -1;+ }++ *ref_out = ref;+ return 0;+}++/**+ * Getters+ */+git_ref_t git_reference_type(const git_reference *ref)+{+ assert(ref);+ return ref->type;+}++const char *git_reference_name(const git_reference *ref)+{+ assert(ref);+ return ref->name;+}++git_repository *git_reference_owner(const git_reference *ref)+{+ assert(ref);+ return ref->db->repo;+}++const git_oid *git_reference_target(const git_reference *ref)+{+ assert(ref);++ if (ref->type != GIT_REF_OID)+ return NULL;++ return &ref->target.oid;+}++const char *git_reference_symbolic_target(const git_reference *ref)+{+ assert(ref);++ if (ref->type != GIT_REF_SYMBOLIC)+ return NULL;++ return ref->target.symbolic;+}++static int reference__create(+ git_reference **ref_out,+ git_repository *repo,+ const char *name,+ const git_oid *oid,+ const char *symbolic,+ int force)+{+ char normalized[GIT_REFNAME_MAX];+ git_refdb *refdb;+ git_reference *ref = NULL;+ int error = 0;+ + if (ref_out)+ *ref_out = NULL;++ if ((error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name)) < 0 ||+ (error = reference_can_write(repo, normalized, NULL, force)) < 0 ||+ (error = git_repository_refdb__weakptr(&refdb, repo)) < 0)+ return error;+ + if ((ref = git_reference__alloc(refdb, name, oid, symbolic)) == NULL)+ return -1;++ if ((error = git_refdb_write(refdb, ref)) < 0) {+ git_reference_free(ref);+ return error;+ }+ + if (ref_out == NULL)+ git_reference_free(ref);+ else+ *ref_out = ref;++ return 0;+}++int git_reference_create(+ git_reference **ref_out,+ git_repository *repo,+ const char *name,+ const git_oid *oid,+ int force)+{+ git_odb *odb;+ int error = 0;++ assert(repo && name && oid);+ + /* Sanity check the reference being created - target must exist. */+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)+ return error;+ + if (!git_odb_exists(odb, oid)) {+ giterr_set(GITERR_REFERENCE,+ "Target OID for the reference doesn't exist on the repository");+ return -1;+ }+ + return reference__create(ref_out, repo, name, oid, NULL, force);+}++int git_reference_symbolic_create(+ git_reference **ref_out,+ git_repository *repo,+ const char *name,+ const char *target,+ int force)+{+ char normalized[GIT_REFNAME_MAX];+ int error = 0;++ assert(repo && name && target);+ + if ((error = git_reference__normalize_name_lax(+ normalized, sizeof(normalized), target)) < 0)+ return error;++ return reference__create(ref_out, repo, name, NULL, normalized, force);+}++int git_reference_set_target(+ git_reference **out,+ git_reference *ref,+ const git_oid *id)+{+ assert(out && ref && id);+ + if (ref->type != GIT_REF_OID) {+ giterr_set(GITERR_REFERENCE, "Cannot set OID on symbolic reference");+ return -1;+ }++ return git_reference_create(out, ref->db->repo, ref->name, id, 1);+}++int git_reference_symbolic_set_target(+ git_reference **out,+ git_reference *ref,+ const char *target)+{+ assert(out && ref && target);+ + if (ref->type != GIT_REF_SYMBOLIC) {+ giterr_set(GITERR_REFERENCE,+ "Cannot set symbolic target on a direct reference");+ return -1;+ }+ + return git_reference_symbolic_create(out, ref->db->repo, ref->name, target, 1);+}++int git_reference_rename(+ git_reference **out,+ git_reference *ref,+ const char *new_name,+ int force)+{+ unsigned int normalization_flags;+ char normalized[GIT_REFNAME_MAX];+ bool should_head_be_updated = false;+ git_reference *result = NULL;+ git_oid *oid;+ const char *symbolic;+ int error = 0;+ int reference_has_log;+ + *out = NULL;++ normalization_flags = ref->type == GIT_REF_SYMBOLIC ?+ GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL;++ if ((error = git_reference_normalize_name(normalized, sizeof(normalized), new_name, normalization_flags)) < 0 ||+ (error = reference_can_write(ref->db->repo, normalized, ref->name, force)) < 0)+ return error;++ /*+ * Create the new reference.+ */+ if (ref->type == GIT_REF_OID) {+ oid = &ref->target.oid;+ symbolic = NULL;+ } else {+ oid = NULL;+ symbolic = ref->target.symbolic;+ }+ + if ((result = git_reference__alloc(ref->db, new_name, oid, symbolic)) == NULL)+ return -1;++ /* Check if we have to update HEAD. */+ if ((error = git_branch_is_head(ref)) < 0)+ goto on_error;++ should_head_be_updated = (error > 0);++ /* Now delete the old ref and save the new one. */+ if ((error = git_refdb_delete(ref->db, ref)) < 0)+ goto on_error;+ + /* Save the new reference. */+ if ((error = git_refdb_write(ref->db, result)) < 0)+ goto rollback;+ + /* Update HEAD it was poiting to the reference being renamed. */+ if (should_head_be_updated && (error = git_repository_set_head(ref->db->repo, new_name)) < 0) {+ giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference");+ goto on_error;+ }++ /* Rename the reflog file, if it exists. */+ reference_has_log = git_reference_has_log(ref);+ if (reference_has_log < 0) {+ error = reference_has_log;+ goto on_error;+ }+ if (reference_has_log && (error = git_reflog_rename(ref, new_name)) < 0)+ goto on_error;++ *out = result;++ return error;++rollback:+ git_refdb_write(ref->db, ref);++on_error:+ git_reference_free(result);++ return error;+}++int git_reference_resolve(git_reference **ref_out, const git_reference *ref)+{+ if (ref->type == GIT_REF_OID)+ return git_reference_lookup(ref_out, ref->db->repo, ref->name);+ else+ return git_reference_lookup_resolved(ref_out, ref->db->repo,+ ref->target.symbolic, -1);+}++int git_reference_foreach(+ git_repository *repo,+ unsigned int list_flags,+ git_reference_foreach_cb callback,+ void *payload)+{+ git_refdb *refdb;+ git_repository_refdb__weakptr(&refdb, repo);++ return git_refdb_foreach(refdb, list_flags, callback, payload);+}++static int cb__reflist_add(const char *ref, void *data)+{+ return git_vector_insert((git_vector *)data, git__strdup(ref));+}++int git_reference_list(+ git_strarray *array,+ git_repository *repo,+ unsigned int list_flags)+{+ git_vector ref_list;++ assert(array && repo);++ array->strings = NULL;+ array->count = 0;++ if (git_vector_init(&ref_list, 8, NULL) < 0)+ return -1;++ if (git_reference_foreach(+ repo, list_flags, &cb__reflist_add, (void *)&ref_list) < 0) {+ git_vector_free(&ref_list);+ return -1;+ }++ array->strings = (char **)ref_list.contents;+ array->count = ref_list.length;+ return 0;+}++static int is_valid_ref_char(char ch)+{+ if ((unsigned) ch <= ' ')+ return 0;++ switch (ch) {+ case '~':+ case '^':+ case ':':+ case '\\':+ case '?':+ case '[':+ case '*':+ return 0;+ default:+ return 1;+ }+}++static int ensure_segment_validity(const char *name)+{+ const char *current = name;+ char prev = '\0';+ const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION);+ int segment_len;++ if (*current == '.')+ return -1; /* Refname starts with "." */++ for (current = name; ; current++) {+ if (*current == '\0' || *current == '/')+ break;++ if (!is_valid_ref_char(*current))+ return -1; /* Illegal character in refname */++ if (prev == '.' && *current == '.')+ return -1; /* Refname contains ".." */++ if (prev == '@' && *current == '{')+ return -1; /* Refname contains "@{" */++ prev = *current;+ }++ segment_len = (int)(current - name);++ /* A refname component can not end with ".lock" */+ if (segment_len >= lock_len &&+ !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len))+ return -1;++ return segment_len;+}++static bool is_all_caps_and_underscore(const char *name, size_t len)+{+ size_t i;+ char c;++ assert(name && len > 0);++ for (i = 0; i < len; i++)+ {+ c = name[i];+ if ((c < 'A' || c > 'Z') && c != '_')+ return false;+ }++ if (*name == '_' || name[len - 1] == '_')+ return false;++ return true;+}++int git_reference__normalize_name(+ git_buf *buf,+ const char *name,+ unsigned int flags)+{+ // Inspired from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/refs.c#L36-100++ char *current;+ int segment_len, segments_count = 0, error = GIT_EINVALIDSPEC;+ unsigned int process_flags;+ bool normalize = (buf != NULL);+ assert(name);++ process_flags = flags;+ current = (char *)name;++ if (*current == '/')+ goto cleanup;++ if (normalize)+ git_buf_clear(buf);++ while (true) {+ segment_len = ensure_segment_validity(current);+ if (segment_len < 0) {+ if ((process_flags & GIT_REF_FORMAT_REFSPEC_PATTERN) &&+ current[0] == '*' &&+ (current[1] == '\0' || current[1] == '/')) {+ /* Accept one wildcard as a full refname component. */+ process_flags &= ~GIT_REF_FORMAT_REFSPEC_PATTERN;+ segment_len = 1;+ } else+ goto cleanup;+ }++ if (segment_len > 0) {+ if (normalize) {+ size_t cur_len = git_buf_len(buf);++ git_buf_joinpath(buf, git_buf_cstr(buf), current);+ git_buf_truncate(buf,+ cur_len + segment_len + (segments_count ? 1 : 0));++ if (git_buf_oom(buf)) {+ error = -1;+ goto cleanup;+ }+ }++ segments_count++;+ }++ /* No empty segment is allowed when not normalizing */+ if (segment_len == 0 && !normalize)+ goto cleanup;++ if (current[segment_len] == '\0')+ break;++ current += segment_len + 1;+ }++ /* A refname can not be empty */+ if (segment_len == 0 && segments_count == 0)+ goto cleanup;++ /* A refname can not end with "." */+ if (current[segment_len - 1] == '.')+ goto cleanup;++ /* A refname can not end with "/" */+ if (current[segment_len - 1] == '/')+ goto cleanup;++ if ((segments_count == 1 ) && !(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL))+ goto cleanup;++ if ((segments_count == 1 ) &&+ !(is_all_caps_and_underscore(name, (size_t)segment_len) ||+ ((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))+ goto cleanup;++ if ((segments_count > 1)+ && (is_all_caps_and_underscore(name, strchr(name, '/') - name)))+ goto cleanup;++ error = 0;++cleanup:+ if (error == GIT_EINVALIDSPEC)+ giterr_set(+ GITERR_REFERENCE,+ "The given reference name '%s' is not valid", name);++ if (error && normalize)+ git_buf_free(buf);++ return error;+}++int git_reference_normalize_name(+ char *buffer_out,+ size_t buffer_size,+ const char *name,+ unsigned int flags)+{+ git_buf buf = GIT_BUF_INIT;+ int error;++ if ((error = git_reference__normalize_name(&buf, name, flags)) < 0)+ goto cleanup;++ if (git_buf_len(&buf) > buffer_size - 1) {+ giterr_set(+ GITERR_REFERENCE,+ "The provided buffer is too short to hold the normalization of '%s'", name);+ error = GIT_EBUFS;+ goto cleanup;+ }++ git_buf_copy_cstr(buffer_out, buffer_size, &buf);++ error = 0;++cleanup:+ git_buf_free(&buf);+ return error;+}++int git_reference__normalize_name_lax(+ char *buffer_out,+ size_t out_size,+ const char *name)+{+ return git_reference_normalize_name(+ buffer_out,+ out_size,+ name,+ GIT_REF_FORMAT_ALLOW_ONELEVEL);+}+#define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC)++int git_reference_cmp(git_reference *ref1, git_reference *ref2)+{+ assert(ref1 && ref2);++ /* let's put symbolic refs before OIDs */+ if (ref1->type != ref2->type)+ return (ref1->type == GIT_REF_SYMBOLIC) ? -1 : 1;++ if (ref1->type == GIT_REF_SYMBOLIC)+ return strcmp(ref1->target.symbolic, ref2->target.symbolic);++ return git_oid_cmp(&ref1->target.oid, &ref2->target.oid);+}++static int reference__update_terminal(+ git_repository *repo,+ const char *ref_name,+ const git_oid *oid,+ int nesting)+{+ git_reference *ref;+ int error = 0;++ if (nesting > MAX_NESTING_LEVEL)+ return GIT_ENOTFOUND;+ + error = git_reference_lookup(&ref, repo, ref_name);++ /* If we haven't found the reference at all, create a new reference. */+ if (error == GIT_ENOTFOUND) {+ giterr_clear();+ return git_reference_create(NULL, repo, ref_name, oid, 0);+ }+ + if (error < 0)+ return error;+ + /* If the ref is a symbolic reference, follow its target. */+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {+ error = reference__update_terminal(repo, git_reference_symbolic_target(ref), oid,+ nesting+1);+ git_reference_free(ref);+ } else {+ git_reference_free(ref);+ error = git_reference_create(NULL, repo, ref_name, oid, 1);+ }+ + return error;+}++/*+ * Starting with the reference given by `ref_name`, follows symbolic+ * references until a direct reference is found and updated the OID+ * on that direct reference to `oid`.+ */+int git_reference__update_terminal(+ git_repository *repo,+ const char *ref_name,+ const git_oid *oid)+{+ return reference__update_terminal(repo, ref_name, oid, 0);+}++int git_reference_foreach_glob(+ git_repository *repo,+ const char *glob,+ unsigned int list_flags,+ int (*callback)(+ const char *reference_name,+ void *payload),+ void *payload)+{+ git_refdb *refdb;++ assert(repo && glob && callback);++ git_repository_refdb__weakptr(&refdb, repo);++ return git_refdb_foreach_glob(refdb, glob, list_flags, callback, payload);+}++int git_reference_has_log(+ git_reference *ref)+{+ git_buf path = GIT_BUF_INIT;+ int result;++ assert(ref);++ if (git_buf_join_n(&path, '/', 3, ref->db->repo->path_repository,+ GIT_REFLOG_DIR, ref->name) < 0)+ return -1;++ result = git_path_isfile(git_buf_cstr(&path));+ git_buf_free(&path);++ return result;+}++int git_reference__is_branch(const char *ref_name)+{+ return git__prefixcmp(ref_name, GIT_REFS_HEADS_DIR) == 0;+}++int git_reference_is_branch(git_reference *ref)+{+ assert(ref);+ return git_reference__is_branch(ref->name);+}++int git_reference__is_remote(const char *ref_name)+{+ return git__prefixcmp(ref_name, GIT_REFS_REMOTES_DIR) == 0;+}++int git_reference_is_remote(git_reference *ref)+{+ assert(ref);+ return git_reference__is_remote(ref->name);+}++static int peel_error(int error, git_reference *ref, const char* msg)+{+ giterr_set(+ GITERR_INVALID,+ "The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg);+ return error;+}++static int reference_target(git_object **object, git_reference *ref)+{+ const git_oid *oid;++ oid = git_reference_target(ref);++ return git_object_lookup(object, git_reference_owner(ref), oid, GIT_OBJ_ANY);+}++int git_reference_peel(+ git_object **peeled,+ git_reference *ref,+ git_otype target_type)+{+ git_reference *resolved = NULL;+ git_object *target = NULL;+ int error;++ assert(ref);++ if ((error = git_reference_resolve(&resolved, ref)) < 0)+ return peel_error(error, ref, "Cannot resolve reference");++ if ((error = reference_target(&target, resolved)) < 0) {+ peel_error(error, ref, "Cannot retrieve reference target");+ goto cleanup;+ }++ if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)+ error = git_object_dup(peeled, target);+ else+ error = git_object_peel(peeled, target, target_type);++cleanup:+ git_object_free(target);+ git_reference_free(resolved);+ return error;+}++int git_reference__is_valid_name(+ const char *refname,+ unsigned int flags)+{+ int error;++ error = git_reference__normalize_name(NULL, refname, flags) == 0;+ giterr_clear();++ return error;+}++int git_reference_is_valid_name(+ const char *refname)+{+ return git_reference__is_valid_name(+ refname,+ GIT_REF_FORMAT_ALLOW_ONELEVEL);+}
libgit2/src/refs.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,7 +10,9 @@ #include "common.h" #include "git2/oid.h" #include "git2/refs.h"+#include "git2/refdb.h" #include "strmap.h"+#include "buffer.h" #define GIT_REFS_DIR "refs/" #define GIT_REFS_HEADS_DIR GIT_REFS_DIR "heads/"@@ -27,33 +29,43 @@ #define GIT_PACKEDREFS_FILE_MODE 0666 #define GIT_HEAD_FILE "HEAD"+#define GIT_ORIG_HEAD_FILE "ORIG_HEAD" #define GIT_FETCH_HEAD_FILE "FETCH_HEAD" #define GIT_MERGE_HEAD_FILE "MERGE_HEAD"+#define GIT_REVERT_HEAD_FILE "REVERT_HEAD"+#define GIT_CHERRY_PICK_HEAD_FILE "CHERRY_PICK_HEAD"+#define GIT_BISECT_LOG_FILE "BISECT_LOG"+#define GIT_REBASE_MERGE_DIR "rebase-merge/"+#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive"+#define GIT_REBASE_APPLY_DIR "rebase-apply/"+#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing"+#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying" #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master" +#define GIT_STASH_FILE "stash"+#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE+ #define GIT_REFNAME_MAX 1024 struct git_reference {- unsigned int flags;- git_repository *owner;- char *name;- time_t mtime;+ git_refdb *db; + git_ref_t type;+ union { git_oid oid; char *symbolic; } target;+ + char name[0]; }; -typedef struct {- git_strmap *packfile;- time_t packfile_time;-} git_refcache;--void git_repository__refcache_free(git_refcache *refs);--int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name);-int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name);+int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name);+int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags);+int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid);+int git_reference__is_valid_name(const char *refname, unsigned int flags);+int git_reference__is_branch(const char *ref_name);+int git_reference__is_remote(const char *ref_name); /** * Lookup a reference by name and try to resolve to an OID.
libgit2/src/refspec.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -11,38 +11,129 @@ #include "refspec.h" #include "util.h" #include "posix.h"+#include "refs.h" -int git_refspec_parse(git_refspec *refspec, const char *str)+int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) {- char *delim;+ // Ported from https://github.com/git/git/blob/f06d47e7e0d9db709ee204ed13a8a7486149f494/remote.c#L518-636 + size_t llen;+ int is_glob = 0;+ const char *lhs, *rhs;+ int flags;++ assert(refspec && input);+ memset(refspec, 0x0, sizeof(git_refspec)); - if (*str == '+') {+ lhs = input;+ if (*lhs == '+') { refspec->force = 1;- str++;+ lhs++; } - delim = strchr(str, ':');- if (delim == NULL) {- refspec->src = git__strdup(str);- GITERR_CHECK_ALLOC(refspec->src);+ rhs = strrchr(lhs, ':');++ /*+ * Before going on, special case ":" (or "+:") as a refspec+ * for matching refs.+ */+ if (!is_fetch && rhs == lhs && rhs[1] == '\0') {+ refspec->matching = 1; return 0; } - refspec->src = git__strndup(str, delim - str);- GITERR_CHECK_ALLOC(refspec->src);+ if (rhs) {+ size_t rlen = strlen(++rhs);+ is_glob = (1 <= rlen && strchr(rhs, '*'));+ refspec->dst = git__strndup(rhs, rlen);+ } - refspec->dst = git__strdup(delim + 1);- if (refspec->dst == NULL) {- git__free(refspec->src);- refspec->src = NULL;- return -1;+ llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));+ if (1 <= llen && memchr(lhs, '*', llen)) {+ if ((rhs && !is_glob) || (!rhs && is_fetch))+ goto invalid;+ is_glob = 1;+ } else if (rhs && is_glob)+ goto invalid;++ refspec->pattern = is_glob;+ refspec->src = git__strndup(lhs, llen);+ flags = GIT_REF_FORMAT_ALLOW_ONELEVEL+ | (is_glob ? GIT_REF_FORMAT_REFSPEC_PATTERN : 0);++ if (is_fetch) {+ /*+ * LHS+ * - empty is allowed; it means HEAD.+ * - otherwise it must be a valid looking ref.+ */+ if (!*refspec->src)+ ; /* empty is ok */+ else if (!git_reference__is_valid_name(refspec->src, flags))+ goto invalid;+ /*+ * RHS+ * - missing is ok, and is same as empty.+ * - empty is ok; it means not to store.+ * - otherwise it must be a valid looking ref.+ */+ if (!refspec->dst)+ ; /* ok */+ else if (!*refspec->dst)+ ; /* ok */+ else if (!git_reference__is_valid_name(refspec->dst, flags))+ goto invalid;+ } else {+ /*+ * LHS+ * - empty is allowed; it means delete.+ * - when wildcarded, it must be a valid looking ref.+ * - otherwise, it must be an extended SHA-1, but+ * there is no existing way to validate this.+ */+ if (!*refspec->src)+ ; /* empty is ok */+ else if (is_glob) {+ if (!git_reference__is_valid_name(refspec->src, flags))+ goto invalid;+ }+ else {+ ; /* anything goes, for now */+ }+ /*+ * RHS+ * - missing is allowed, but LHS then must be a+ * valid looking ref.+ * - empty is not allowed.+ * - otherwise it must be a valid looking ref.+ */+ if (!refspec->dst) {+ if (!git_reference__is_valid_name(refspec->src, flags))+ goto invalid;+ } else if (!*refspec->dst) {+ goto invalid;+ } else {+ if (!git_reference__is_valid_name(refspec->dst, flags))+ goto invalid;+ } } return 0;++ invalid:+ return -1; } +void git_refspec__free(git_refspec *refspec)+{+ if (refspec == NULL)+ return;++ git__free(refspec->src);+ git__free(refspec->dst);+}+ const char *git_refspec_src(const git_refspec *refspec) { return refspec == NULL ? NULL : refspec->src;@@ -53,6 +144,13 @@ return refspec == NULL ? NULL : refspec->dst; } +int git_refspec_force(const git_refspec *refspec)+{+ assert(refspec);++ return refspec->force;+}+ int git_refspec_src_matches(const git_refspec *refspec, const char *refname) { if (refspec == NULL || refspec->src == NULL)@@ -61,11 +159,19 @@ return (p_fnmatch(refspec->src, refname, 0) == 0); } -int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name)+int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) {+ if (refspec == NULL || refspec->dst == NULL)+ return false;++ return (p_fnmatch(refspec->dst, refname, 0) == 0);+}++static int refspec_transform_internal(char *out, size_t outlen, const char *from, const char *to, const char *name)+{ size_t baselen, namelen; - baselen = strlen(spec->dst);+ baselen = strlen(to); if (outlen <= baselen) { giterr_set(GITERR_INVALID, "Reference name too long"); return GIT_EBUFS;@@ -75,8 +181,8 @@ * No '*' at the end means that it's mapped to one specific local * branch, so no actual transformation is needed. */- if (spec->dst[baselen - 1] != '*') {- memcpy(out, spec->dst, baselen + 1); /* include '\0' */+ if (to[baselen - 1] != '*') {+ memcpy(out, to, baselen + 1); /* include '\0' */ return 0; } @@ -84,7 +190,7 @@ baselen--; /* skip the prefix, -1 is for the '*' */- name += strlen(spec->src) - 1;+ name += strlen(from) - 1; namelen = strlen(name); @@ -93,26 +199,36 @@ return GIT_EBUFS; } - memcpy(out, spec->dst, baselen);+ memcpy(out, to, baselen); memcpy(out + baselen, name, namelen + 1); return 0; } -int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name)+int git_refspec_transform(char *out, size_t outlen, const git_refspec *spec, const char *name) {- if (git_buf_sets(out, spec->dst) < 0)+ return refspec_transform_internal(out, outlen, spec->src, spec->dst, name);+}++int git_refspec_rtransform(char *out, size_t outlen, const git_refspec *spec, const char *name)+{+ return refspec_transform_internal(out, outlen, spec->dst, spec->src, name);+}++static int refspec_transform(git_buf *out, const char *from, const char *to, const char *name)+{+ if (git_buf_sets(out, to) < 0) return -1; /*- * No '*' at the end means that it's mapped to one specific local+ * No '*' at the end means that it's mapped to one specific * branch, so no actual transformation is needed. */ if (git_buf_len(out) > 0 && out->ptr[git_buf_len(out) - 1] != '*') return 0; git_buf_truncate(out, git_buf_len(out) - 1); /* remove trailing '*' */- git_buf_puts(out, name + strlen(spec->src) - 1);+ git_buf_puts(out, name + strlen(from) - 1); if (git_buf_oom(out)) return -1;@@ -120,3 +236,31 @@ return 0; } +int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name)+{+ return refspec_transform(out, spec->src, spec->dst, name);+}++int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name)+{+ return refspec_transform(out, spec->dst, spec->src, name);+}++int git_refspec__serialize(git_buf *out, const git_refspec *refspec)+{+ if (refspec->force)+ git_buf_putc(out, '+');++ git_buf_printf(out, "%s:%s",+ refspec->src != NULL ? refspec->src : "",+ refspec->dst != NULL ? refspec->dst : "");++ return git_buf_oom(out) == false;+}++int git_refspec_is_wildcard(const git_refspec *spec)+{+ assert(spec && spec->src);++ return (spec->src[strlen(spec->src) - 1] == '*');+}
libgit2/src/refspec.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -19,8 +19,16 @@ matching :1; }; +#define GIT_REFSPEC_TAGS "refs/tags/*:refs/tags/*"+ int git_refspec_parse(struct git_refspec *refspec, const char *str);+int git_refspec__parse(+ struct git_refspec *refspec,+ const char *str,+ bool is_fetch); +void git_refspec__free(git_refspec *refspec);+ /** * Transform a reference to its target following the refspec's rules, * and writes the results into a git_buf.@@ -31,5 +39,26 @@ * @return 0 or error if buffer allocation fails */ int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *name);++/**+ * Transform a reference from its target following the refspec's rules,+ * and writes the results into a git_buf.+ *+ * @param out where to store the source name+ * @param spec the refspec+ * @param name the name of the reference to transform+ * @return 0 or error if buffer allocation fails+ */+int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name);++int git_refspec__serialize(git_buf *out, const git_refspec *refspec);++/**+ * Determines if a refspec is a wildcard refspec.+ *+ * @param spec the refspec+ * @return 1 if the refspec is a wildcard, 0 otherwise+ */+int git_refspec_is_wildcard(const git_refspec *spec); #endif
libgit2/src/remote.c view
@@ -1,510 +1,1391 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#include "git2/remote.h"-#include "git2/config.h"-#include "git2/types.h"--#include "config.h"-#include "repository.h"-#include "remote.h"-#include "fetch.h"-#include "refs.h"--#include <regex.h>--static int refspec_parse(git_refspec *refspec, const char *str)-{- char *delim;-- memset(refspec, 0x0, sizeof(git_refspec));-- if (*str == '+') {- refspec->force = 1;- str++;- }-- delim = strchr(str, ':');- if (delim == NULL) {- giterr_set(GITERR_NET, "Invalid refspec, missing ':'");- return -1;- }-- refspec->src = git__strndup(str, delim - str);- GITERR_CHECK_ALLOC(refspec->src);-- refspec->dst = git__strdup(delim + 1);- GITERR_CHECK_ALLOC(refspec->dst);-- return 0;-}--static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var)-{- int error;- const char *val;-- if ((error = git_config_get_string(&val, cfg, var)) < 0)- return error;-- return refspec_parse(refspec, val);-}--int git_remote_new(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)-{- git_remote *remote;-- /* name is optional */- assert(out && repo && url);-- remote = git__malloc(sizeof(git_remote));- GITERR_CHECK_ALLOC(remote);-- memset(remote, 0x0, sizeof(git_remote));- remote->repo = repo;-- if (git_vector_init(&remote->refs, 32, NULL) < 0)- return -1;-- remote->url = git__strdup(url);- GITERR_CHECK_ALLOC(remote->url);-- if (name != NULL) {- remote->name = git__strdup(name);- GITERR_CHECK_ALLOC(remote->name);- }-- if (fetch != NULL) {- if (refspec_parse(&remote->fetch, fetch) < 0)- goto on_error;- }-- *out = remote;- return 0;--on_error:- git_remote_free(remote);- return -1;-}--int git_remote_load(git_remote **out, git_repository *repo, const char *name)-{- git_remote *remote;- git_buf buf = GIT_BUF_INIT;- const char *val;- int error = 0;- git_config *config;-- assert(out && repo && name);-- if (git_repository_config__weakptr(&config, repo) < 0)- return -1;-- remote = git__malloc(sizeof(git_remote));- GITERR_CHECK_ALLOC(remote);-- memset(remote, 0x0, sizeof(git_remote));- remote->name = git__strdup(name);- GITERR_CHECK_ALLOC(remote->name);-- if (git_vector_init(&remote->refs, 32, NULL) < 0) {- error = -1;- goto cleanup;- }-- if (git_buf_printf(&buf, "remote.%s.url", name) < 0) {- error = -1;- goto cleanup;- }-- if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0)- goto cleanup;-- remote->repo = repo;- remote->url = git__strdup(val);- GITERR_CHECK_ALLOC(remote->url);-- git_buf_clear(&buf);- if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) {- error = -1;- goto cleanup;- }-- error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf));- if (error == GIT_ENOTFOUND)- error = 0;-- if (error < 0) {- error = -1;- goto cleanup;- }-- git_buf_clear(&buf);- if (git_buf_printf(&buf, "remote.%s.push", name) < 0) {- error = -1;- goto cleanup;- }-- error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf));- if (error == GIT_ENOTFOUND)- error = 0;-- if (error < 0) {- error = -1;- goto cleanup;- }-- *out = remote;--cleanup:- git_buf_free(&buf);-- if (error < 0)- git_remote_free(remote);-- return error;-}--int git_remote_save(const git_remote *remote)-{- git_config *config;- git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT;-- if (git_repository_config__weakptr(&config, remote->repo) < 0)- return -1;-- if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0)- return -1;-- if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) {- git_buf_free(&buf);- return -1;- }-- if (remote->fetch.src != NULL && remote->fetch.dst != NULL) {- git_buf_clear(&buf);- git_buf_clear(&value);- git_buf_printf(&buf, "remote.%s.fetch", remote->name);- git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst);- if (git_buf_oom(&buf) || git_buf_oom(&value))- return -1;-- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)- goto on_error;- }-- if (remote->push.src != NULL && remote->push.dst != NULL) {- git_buf_clear(&buf);- git_buf_clear(&value);- git_buf_printf(&buf, "remote.%s.push", remote->name);- git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst);- if (git_buf_oom(&buf) || git_buf_oom(&value))- return -1;-- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)- goto on_error;- }-- git_buf_free(&buf);- git_buf_free(&value);-- return 0;--on_error:- git_buf_free(&buf);- git_buf_free(&value);- return -1;-}--const char *git_remote_name(git_remote *remote)-{- assert(remote);- return remote->name;-}--const char *git_remote_url(git_remote *remote)-{- assert(remote);- return remote->url;-}--int git_remote_set_fetchspec(git_remote *remote, const char *spec)-{- git_refspec refspec;-- assert(remote && spec);-- if (refspec_parse(&refspec, spec) < 0)- return -1;-- git__free(remote->fetch.src);- git__free(remote->fetch.dst);- remote->fetch.src = refspec.src;- remote->fetch.dst = refspec.dst;-- return 0;-}--const git_refspec *git_remote_fetchspec(git_remote *remote)-{- assert(remote);- return &remote->fetch;-}--int git_remote_set_pushspec(git_remote *remote, const char *spec)-{- git_refspec refspec;-- assert(remote && spec);-- if (refspec_parse(&refspec, spec) < 0)- return -1;-- git__free(remote->push.src);- git__free(remote->push.dst);- remote->push.src = refspec.src;- remote->push.dst = refspec.dst;-- return 0;-}--const git_refspec *git_remote_pushspec(git_remote *remote)-{- assert(remote);- return &remote->push;-}--int git_remote_connect(git_remote *remote, int direction)-{- git_transport *t;-- assert(remote);-- if (git_transport_new(&t, remote->url) < 0)- return -1;-- if (t->connect(t, direction) < 0) {- goto on_error;- }-- remote->transport = t;-- return 0;--on_error:- t->free(t);- return -1;-}--int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)-{- assert(remote);-- if (!remote->transport || !remote->transport->connected) {- giterr_set(GITERR_NET, "The remote is not connected");- return -1;- }-- return remote->transport->ls(remote->transport, list_cb, payload);-}--int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats)-{- int error;-- assert(remote && bytes && stats);-- if ((error = git_fetch_negotiate(remote)) < 0)- return error;-- return git_fetch_download_pack(remote, bytes, stats);-}--int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b))-{- int error = 0;- unsigned int i = 0;- git_buf refname = GIT_BUF_INIT;- git_oid old;- git_vector *refs = &remote->refs;- git_remote_head *head;- git_reference *ref;- struct git_refspec *spec = &remote->fetch;-- assert(remote);-- if (refs->length == 0)- return 0;-- /* HEAD is only allowed to be the first in the list */- head = refs->contents[0];- if (!strcmp(head->name, GIT_HEAD_FILE)) {- if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)- return -1;-- i = 1;- git_reference_free(ref);- }-- for (; i < refs->length; ++i) {- head = refs->contents[i];-- if (git_refspec_transform_r(&refname, spec, head->name) < 0)- goto on_error;-- error = git_reference_name_to_oid(&old, remote->repo, refname.ptr);- if (error < 0 && error != GIT_ENOTFOUND)- goto on_error;-- if (error == GIT_ENOTFOUND)- memset(&old, 0, GIT_OID_RAWSZ);-- if (!git_oid_cmp(&old, &head->oid))- continue;-- if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0)- break;-- git_reference_free(ref);-- if (cb != NULL) {- if (cb(refname.ptr, &old, &head->oid) < 0)- goto on_error;- }- }-- git_buf_free(&refname);- return 0;--on_error:- git_buf_free(&refname);- return -1;--}--int git_remote_connected(git_remote *remote)-{- assert(remote);- return remote->transport == NULL ? 0 : remote->transport->connected;-}--void git_remote_disconnect(git_remote *remote)-{- assert(remote);-- if (remote->transport != NULL && remote->transport->connected)- remote->transport->close(remote->transport);-}--void git_remote_free(git_remote *remote)-{- if (remote == NULL)- return;-- if (remote->transport != NULL) {- git_remote_disconnect(remote);-- remote->transport->free(remote->transport);- remote->transport = NULL;- }-- git_vector_free(&remote->refs);-- git__free(remote->fetch.src);- git__free(remote->fetch.dst);- git__free(remote->push.src);- git__free(remote->push.dst);- git__free(remote->url);- git__free(remote->name);- git__free(remote);-}--struct cb_data {- git_vector *list;- regex_t *preg;-};--static int remote_list_cb(const char *name, const char *value, void *data_)-{- struct cb_data *data = (struct cb_data *)data_;- size_t nmatch = 2;- regmatch_t pmatch[2];- GIT_UNUSED(value);-- if (!regexec(data->preg, name, nmatch, pmatch, 0)) {- char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so);- GITERR_CHECK_ALLOC(remote_name);-- if (git_vector_insert(data->list, remote_name) < 0)- return -1;- }-- return 0;-}--int git_remote_list(git_strarray *remotes_list, git_repository *repo)-{- git_config *cfg;- git_vector list;- regex_t preg;- struct cb_data data;- int error;-- if (git_repository_config__weakptr(&cfg, repo) < 0)- return -1;-- if (git_vector_init(&list, 4, NULL) < 0)- return -1;-- if (regcomp(&preg, "^remote\\.(.*)\\.url$", REG_EXTENDED) < 0) {- giterr_set(GITERR_OS, "Remote catch regex failed to compile");- return -1;- }-- data.list = &list;- data.preg = &preg;- error = git_config_foreach(cfg, remote_list_cb, &data);- regfree(&preg);- if (error < 0) {- size_t i;- char *elem;- git_vector_foreach(&list, i, elem) {- git__free(elem);- }-- git_vector_free(&list);- return error;- }-- remotes_list->strings = (char **)list.contents;- remotes_list->count = list.length;-- return 0;-}--int git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url)-{- git_buf buf = GIT_BUF_INIT;-- if (git_buf_printf(&buf, "refs/heads/*:refs/remotes/%s/*", name) < 0)- return -1;-- if (git_remote_new(out, repo, name, url, git_buf_cstr(&buf)) < 0)- goto on_error;-- git_buf_free(&buf);-- if (git_remote_save(*out) < 0)- goto on_error;-- return 0;--on_error:- git_buf_free(&buf);- git_remote_free(*out);- return -1;+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "git2/config.h"+#include "git2/types.h"+#include "git2/oid.h"++#include "config.h"+#include "repository.h"+#include "remote.h"+#include "fetch.h"+#include "refs.h"+#include "refspec.h"+#include "fetchhead.h"++#include <regex.h>++static int parse_remote_refspec(git_config *cfg, git_refspec *refspec, const char *var, bool is_fetch)+{+ int error;+ const char *val;++ if ((error = git_config_get_string(&val, cfg, var)) < 0)+ return error;++ return git_refspec__parse(refspec, val, is_fetch);+}++static int download_tags_value(git_remote *remote, git_config *cfg)+{+ const char *val;+ git_buf buf = GIT_BUF_INIT;+ int error;++ if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSET)+ return 0;++ /* This is the default, let's see if we need to change it */+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;+ if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)+ return -1;++ error = git_config_get_string(&val, cfg, git_buf_cstr(&buf));+ git_buf_free(&buf);+ if (!error && !strcmp(val, "--no-tags"))+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;+ else if (!error && !strcmp(val, "--tags"))+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL;++ if (error == GIT_ENOTFOUND)+ error = 0;++ return error;+}++static int ensure_remote_name_is_valid(const char *name)+{+ int error = 0;++ if (!git_remote_is_valid_name(name)) {+ giterr_set(+ GITERR_CONFIG,+ "'%s' is not a valid remote name.", name);+ error = GIT_EINVALIDSPEC;+ }++ return error;+}++static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)+{+ git_remote *remote;+ git_buf fetchbuf = GIT_BUF_INIT;+ int error = -1;++ /* name is optional */+ assert(out && repo && url);++ remote = git__calloc(1, sizeof(git_remote));+ GITERR_CHECK_ALLOC(remote);++ remote->repo = repo;+ remote->check_cert = 1;+ remote->update_fetchhead = 1;++ if (git_vector_init(&remote->refs, 32, NULL) < 0)+ goto on_error;++ remote->url = git__strdup(url);+ GITERR_CHECK_ALLOC(remote->url);++ if (name != NULL) {+ remote->name = git__strdup(name);+ GITERR_CHECK_ALLOC(remote->name);+ }++ if (fetch != NULL) {+ if (git_refspec__parse(&remote->fetch, fetch, true) < 0)+ goto on_error;+ }++ /* A remote without a name doesn't download tags */+ if (!name) {+ remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;+ }++ *out = remote;+ git_buf_free(&fetchbuf);+ return 0;++on_error:+ git_remote_free(remote);+ git_buf_free(&fetchbuf);+ return error;+}++static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)+{+ int error;+ git_remote *remote;++ error = git_remote_load(&remote, repo, name);++ if (error == GIT_ENOTFOUND)+ return 0;++ if (error < 0)+ return error;++ git_remote_free(remote);++ giterr_set(+ GITERR_CONFIG,+ "Remote '%s' already exists.", name);++ return GIT_EEXISTS;+}+++int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)+{+ git_buf buf = GIT_BUF_INIT;+ git_remote *remote = NULL;+ int error;++ if ((error = ensure_remote_name_is_valid(name)) < 0)+ return error;++ if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)+ return error;++ if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)+ return -1;++ if (create_internal(&remote, repo, name, url, git_buf_cstr(&buf)) < 0)+ goto on_error;++ git_buf_free(&buf);++ if (git_remote_save(remote) < 0)+ goto on_error;++ *out = remote;++ return 0;++on_error:+ git_buf_free(&buf);+ git_remote_free(remote);+ return -1;+}++int git_remote_create_inmemory(git_remote **out, git_repository *repo, const char *fetch, const char *url)+{+ int error;+ git_remote *remote;++ if ((error = create_internal(&remote, repo, NULL, url, fetch)) < 0)+ return error;++ *out = remote;+ return 0;+}++int git_remote_load(git_remote **out, git_repository *repo, const char *name)+{+ git_remote *remote;+ git_buf buf = GIT_BUF_INIT;+ const char *val;+ int error = 0;+ git_config *config;++ assert(out && repo && name);++ if ((error = ensure_remote_name_is_valid(name)) < 0)+ return error;++ if (git_repository_config__weakptr(&config, repo) < 0)+ return -1;++ remote = git__malloc(sizeof(git_remote));+ GITERR_CHECK_ALLOC(remote);++ memset(remote, 0x0, sizeof(git_remote));+ remote->check_cert = 1;+ remote->update_fetchhead = 1;+ remote->name = git__strdup(name);+ GITERR_CHECK_ALLOC(remote->name);++ if (git_vector_init(&remote->refs, 32, NULL) < 0) {+ error = -1;+ goto cleanup;+ }++ if (git_buf_printf(&buf, "remote.%s.url", name) < 0) {+ error = -1;+ goto cleanup;+ }++ if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0)+ goto cleanup;+ + if (strlen(val) == 0) {+ giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name);+ error = -1;+ goto cleanup;+ }++ remote->repo = repo;+ remote->url = git__strdup(val);+ GITERR_CHECK_ALLOC(remote->url);++ git_buf_clear(&buf);+ if (git_buf_printf(&buf, "remote.%s.pushurl", name) < 0) {+ error = -1;+ goto cleanup;+ }++ error = git_config_get_string(&val, config, git_buf_cstr(&buf));+ if (error == GIT_ENOTFOUND) {+ val = NULL;+ error = 0;+ }++ if (error < 0) {+ error = -1;+ goto cleanup;+ }++ if (val) {+ remote->pushurl = git__strdup(val);+ GITERR_CHECK_ALLOC(remote->pushurl);+ }++ git_buf_clear(&buf);+ if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) {+ error = -1;+ goto cleanup;+ }++ error = parse_remote_refspec(config, &remote->fetch, git_buf_cstr(&buf), true);+ if (error == GIT_ENOTFOUND)+ error = 0;++ if (error < 0) {+ error = -1;+ goto cleanup;+ }++ git_buf_clear(&buf);+ if (git_buf_printf(&buf, "remote.%s.push", name) < 0) {+ error = -1;+ goto cleanup;+ }++ error = parse_remote_refspec(config, &remote->push, git_buf_cstr(&buf), false);+ if (error == GIT_ENOTFOUND)+ error = 0;++ if (error < 0) {+ error = -1;+ goto cleanup;+ }++ if (download_tags_value(remote, config) < 0)+ goto cleanup;++ *out = remote;++cleanup:+ git_buf_free(&buf);++ if (error < 0)+ git_remote_free(remote);++ return error;+}++static int update_config_refspec(+ git_config *config,+ const char *remote_name,+ const git_refspec *refspec,+ int git_direction)+{+ git_buf name = GIT_BUF_INIT, value = GIT_BUF_INIT;+ int error = -1;++ if (refspec->src == NULL || refspec->dst == NULL)+ return 0;++ if (git_buf_printf(+ &name,+ "remote.%s.%s",+ remote_name,+ git_direction == GIT_DIRECTION_FETCH ? "fetch" : "push") < 0)+ goto cleanup;++ if (git_refspec__serialize(&value, refspec) < 0)+ goto cleanup;++ error = git_config_set_string(+ config,+ git_buf_cstr(&name),+ git_buf_cstr(&value));++cleanup:+ git_buf_free(&name);+ git_buf_free(&value);++ return error;+}++int git_remote_save(const git_remote *remote)+{+ int error;+ git_config *config;+ const char *tagopt = NULL;+ git_buf buf = GIT_BUF_INIT;++ assert(remote);++ if (!remote->name) {+ giterr_set(GITERR_INVALID, "Can't save an in-memory remote.");+ return GIT_EINVALIDSPEC;+ }++ if ((error = ensure_remote_name_is_valid(remote->name)) < 0)+ return error;++ if (git_repository_config__weakptr(&config, remote->repo) < 0)+ return -1;++ if (git_buf_printf(&buf, "remote.%s.url", remote->name) < 0)+ return -1;++ if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) {+ git_buf_free(&buf);+ return -1;+ }++ git_buf_clear(&buf);+ if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0)+ return -1;++ if (remote->pushurl) {+ if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) {+ git_buf_free(&buf);+ return -1;+ }+ } else {+ int error = git_config_delete_entry(config, git_buf_cstr(&buf));+ if (error == GIT_ENOTFOUND) {+ error = 0;+ giterr_clear();+ }+ if (error < 0) {+ git_buf_free(&buf);+ return -1;+ }+ }++ if (update_config_refspec(+ config,+ remote->name,+ &remote->fetch,+ GIT_DIRECTION_FETCH) < 0)+ goto on_error;++ if (update_config_refspec(+ config,+ remote->name,+ &remote->push,+ GIT_DIRECTION_PUSH) < 0)+ goto on_error;++ /*+ * What action to take depends on the old and new values. This+ * is describes by the table below. tagopt means whether the+ * is already a value set in the config+ *+ * AUTO ALL or NONE+ * +-----------------------++ * tagopt | remove | set |+ * +---------+-------------|+ * !tagopt | nothing | set |+ * +---------+-------------++ */++ git_buf_clear(&buf);+ if (git_buf_printf(&buf, "remote.%s.tagopt", remote->name) < 0)+ goto on_error;++ error = git_config_get_string(&tagopt, config, git_buf_cstr(&buf));+ if (error < 0 && error != GIT_ENOTFOUND)+ goto on_error;++ if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {+ if (git_config_set_string(config, git_buf_cstr(&buf), "--tags") < 0)+ goto on_error;+ } else if (remote->download_tags == GIT_REMOTE_DOWNLOAD_TAGS_NONE) {+ if (git_config_set_string(config, git_buf_cstr(&buf), "--no-tags") < 0)+ goto on_error;+ } else if (tagopt) {+ if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)+ goto on_error;+ }++ git_buf_free(&buf);++ return 0;++on_error:+ git_buf_free(&buf);+ return -1;+}++const char *git_remote_name(const git_remote *remote)+{+ assert(remote);+ return remote->name;+}++const char *git_remote_url(const git_remote *remote)+{+ assert(remote);+ return remote->url;+}++int git_remote_set_url(git_remote *remote, const char* url)+{+ assert(remote);+ assert(url);++ git__free(remote->url);+ remote->url = git__strdup(url);+ GITERR_CHECK_ALLOC(remote->url);++ return 0;+}++const char *git_remote_pushurl(const git_remote *remote)+{+ assert(remote);+ return remote->pushurl;+}++int git_remote_set_pushurl(git_remote *remote, const char* url)+{+ assert(remote);++ git__free(remote->pushurl);+ if (url) {+ remote->pushurl = git__strdup(url);+ GITERR_CHECK_ALLOC(remote->pushurl);+ } else {+ remote->pushurl = NULL;+ }+ return 0;+}++int git_remote_set_fetchspec(git_remote *remote, const char *spec)+{+ git_refspec refspec;++ assert(remote && spec);++ if (git_refspec__parse(&refspec, spec, true) < 0)+ return -1;++ git_refspec__free(&remote->fetch);+ memcpy(&remote->fetch, &refspec, sizeof(git_refspec));++ return 0;+}++const git_refspec *git_remote_fetchspec(const git_remote *remote)+{+ assert(remote);+ return &remote->fetch;+}++int git_remote_set_pushspec(git_remote *remote, const char *spec)+{+ git_refspec refspec;++ assert(remote && spec);++ if (git_refspec__parse(&refspec, spec, false) < 0)+ return -1;++ git_refspec__free(&remote->push);+ remote->push.src = refspec.src;+ remote->push.dst = refspec.dst;++ return 0;+}++const git_refspec *git_remote_pushspec(const git_remote *remote)+{+ assert(remote);+ return &remote->push;+}++const char* git_remote__urlfordirection(git_remote *remote, int direction)+{+ assert(remote);++ if (direction == GIT_DIRECTION_FETCH) {+ return remote->url;+ }++ if (direction == GIT_DIRECTION_PUSH) {+ return remote->pushurl ? remote->pushurl : remote->url;+ }++ return NULL;+}++int git_remote_connect(git_remote *remote, git_direction direction)+{+ git_transport *t;+ const char *url;+ int flags = GIT_TRANSPORTFLAGS_NONE;++ assert(remote);++ t = remote->transport;++ url = git_remote__urlfordirection(remote, direction);+ if (url == NULL )+ return -1;++ /* A transport could have been supplied in advance with+ * git_remote_set_transport */+ if (!t && git_transport_new(&t, remote, url) < 0)+ return -1;++ if (t->set_callbacks &&+ t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.payload) < 0)+ goto on_error;++ if (!remote->check_cert)+ flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;++ if (t->connect(t, url, remote->cred_acquire_cb, remote->cred_acquire_payload, direction, flags) < 0)+ goto on_error;++ remote->transport = t;++ return 0;++on_error:+ t->free(t);++ if (t == remote->transport)+ remote->transport = NULL;++ return -1;+}++int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)+{+ assert(remote);++ return remote->transport->ls(remote->transport, list_cb, payload);+}++int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url)+{+ git_config *cfg;+ const char *val;++ assert(remote);++ if (!proxy_url || !remote->repo)+ return -1;++ *proxy_url = NULL;++ if (git_repository_config__weakptr(&cfg, remote->repo) < 0)+ return -1;++ /* Go through the possible sources for proxy configuration, from most specific+ * to least specific. */++ /* remote.<name>.proxy config setting */+ if (remote->name && 0 != *(remote->name)) {+ git_buf buf = GIT_BUF_INIT;++ if (git_buf_printf(&buf, "remote.%s.proxy", remote->name) < 0)+ return -1;++ if (!git_config_get_string(&val, cfg, git_buf_cstr(&buf)) &&+ val && ('\0' != *val)) {+ git_buf_free(&buf);++ *proxy_url = git__strdup(val);+ GITERR_CHECK_ALLOC(*proxy_url);+ return 0;+ }++ git_buf_free(&buf);+ }++ /* http.proxy config setting */+ if (!git_config_get_string(&val, cfg, "http.proxy") &&+ val && ('\0' != *val)) {+ *proxy_url = git__strdup(val);+ GITERR_CHECK_ALLOC(*proxy_url);+ return 0;+ }++ /* HTTP_PROXY / HTTPS_PROXY environment variables */+ val = use_ssl ? getenv("HTTPS_PROXY") : getenv("HTTP_PROXY");++ if (val && ('\0' != *val)) {+ *proxy_url = git__strdup(val);+ GITERR_CHECK_ALLOC(*proxy_url);+ return 0;+ }++ return 0;+}++int git_remote_download(+ git_remote *remote,+ git_transfer_progress_callback progress_cb,+ void *progress_payload)+{+ int error;++ assert(remote);++ if ((error = git_fetch_negotiate(remote)) < 0)+ return error;++ return git_fetch_download_pack(remote, progress_cb, progress_payload);+}++static int update_tips_callback(git_remote_head *head, void *payload)+{+ git_vector *refs = (git_vector *)payload;++ return git_vector_insert(refs, head);+}++static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src)+{+ unsigned int i;+ git_remote_head *remote_ref;++ assert(update_heads && fetchspec_src);++ *out = NULL;++ git_vector_foreach(update_heads, i, remote_ref) {+ if (strcmp(remote_ref->name, fetchspec_src) == 0) {+ *out = remote_ref;+ break;+ }+ }++ return 0;+}++static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref)+{+ git_reference *resolved_ref = NULL;+ git_reference *tracking_ref = NULL;+ git_buf remote_name = GIT_BUF_INIT;+ int error = 0;++ assert(out && remote && ref);++ *out = NULL;++ if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 ||+ (!git_reference_is_branch(resolved_ref)) ||+ (error = git_branch_upstream(&tracking_ref, resolved_ref)) < 0 ||+ (error = git_refspec_transform_l(&remote_name, &remote->fetch, git_reference_name(tracking_ref))) < 0) {+ /* Not an error if HEAD is orphaned or no tracking branch */+ if (error == GIT_ENOTFOUND)+ error = 0;++ goto cleanup;+ }++ error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name));++cleanup:+ git_reference_free(tracking_ref);+ git_reference_free(resolved_ref);+ git_buf_free(&remote_name);+ return error;+}++static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_heads)+{+ struct git_refspec *spec;+ git_reference *head_ref = NULL;+ git_fetchhead_ref *fetchhead_ref;+ git_remote_head *remote_ref, *merge_remote_ref;+ git_vector fetchhead_refs;+ bool include_all_fetchheads;+ unsigned int i = 0;+ int error = 0;++ assert(remote);++ /* no heads, nothing to do */+ if (update_heads->length == 0)+ return 0;++ spec = &remote->fetch;++ if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0)+ return -1;++ /* Iff refspec is * (but not subdir slash star), include tags */+ include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0);++ /* Determine what to merge: if refspec was a wildcard, just use HEAD */+ if (git_refspec_is_wildcard(spec)) {+ if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 ||+ (error = remote_head_for_ref(&merge_remote_ref, remote, update_heads, head_ref)) < 0)+ goto cleanup;+ } else {+ /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */+ if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0)+ goto cleanup;+ }++ /* Create the FETCH_HEAD file */+ git_vector_foreach(update_heads, i, remote_ref) {+ int merge_this_fetchhead = (merge_remote_ref == remote_ref);++ if (!include_all_fetchheads &&+ !git_refspec_src_matches(spec, remote_ref->name) &&+ !merge_this_fetchhead)+ continue;++ if (git_fetchhead_ref_create(&fetchhead_ref,+ &remote_ref->oid,+ merge_this_fetchhead,+ remote_ref->name,+ git_remote_url(remote)) < 0)+ goto cleanup;++ if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0)+ goto cleanup;+ }++ git_fetchhead_write(remote->repo, &fetchhead_refs);++cleanup:+ for (i = 0; i < fetchhead_refs.length; ++i)+ git_fetchhead_ref_free(fetchhead_refs.contents[i]);++ git_vector_free(&fetchhead_refs);+ git_reference_free(head_ref);++ return error;+}++int git_remote_update_tips(git_remote *remote)+{+ int error = 0, autotag;+ unsigned int i = 0;+ git_buf refname = GIT_BUF_INIT;+ git_oid old;+ git_odb *odb;+ git_remote_head *head;+ git_reference *ref;+ struct git_refspec *spec;+ git_refspec tagspec;+ git_vector refs, update_heads;++ assert(remote);++ spec = &remote->fetch;+ + if (git_repository_odb__weakptr(&odb, remote->repo) < 0)+ return -1;++ if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)+ return -1;++ /* Make a copy of the transport's refs */+ if (git_vector_init(&refs, 16, NULL) < 0 ||+ git_vector_init(&update_heads, 16, NULL) < 0)+ return -1;++ if (git_remote_ls(remote, update_tips_callback, &refs) < 0)+ goto on_error;++ /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */+ if (refs.length > 0) {+ head = (git_remote_head *)refs.contents[0];++ if (!strcmp(head->name, GIT_HEAD_FILE)) {+ if (git_reference_create(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)+ goto on_error;++ i = 1;+ git_reference_free(ref);+ }+ }++ for (; i < refs.length; ++i) {+ head = (git_remote_head *)refs.contents[i];+ autotag = 0;++ /* Ignore malformed ref names (which also saves us from tag^{} */+ if (!git_reference_is_valid_name(head->name))+ continue;++ if (git_refspec_src_matches(spec, head->name)) {+ if (git_refspec_transform_r(&refname, spec, head->name) < 0)+ goto on_error;+ } else if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {++ if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_ALL)+ autotag = 1;++ if (!git_refspec_src_matches(&tagspec, head->name))+ continue;++ git_buf_clear(&refname);+ if (git_buf_puts(&refname, head->name) < 0)+ goto on_error;+ } else {+ continue;+ }++ if (autotag && !git_odb_exists(odb, &head->oid))+ continue;++ if (git_vector_insert(&update_heads, head) < 0)+ goto on_error;++ error = git_reference_name_to_id(&old, remote->repo, refname.ptr);+ if (error < 0 && error != GIT_ENOTFOUND)+ goto on_error;++ if (error == GIT_ENOTFOUND)+ memset(&old, 0, GIT_OID_RAWSZ);++ if (!git_oid_cmp(&old, &head->oid))+ continue;++ /* In autotag mode, don't overwrite any locally-existing tags */+ error = git_reference_create(&ref, remote->repo, refname.ptr, &head->oid, !autotag);+ if (error < 0 && error != GIT_EEXISTS)+ goto on_error;++ git_reference_free(ref);++ if (remote->callbacks.update_tips != NULL) {+ if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.payload) < 0)+ goto on_error;+ }+ }++ if (git_remote_update_fetchhead(remote) &&+ (error = git_remote_write_fetchhead(remote, &update_heads)) < 0)+ goto on_error;++ git_vector_free(&refs);+ git_vector_free(&update_heads);+ git_refspec__free(&tagspec);+ git_buf_free(&refname);+ return 0;++on_error:+ git_vector_free(&refs);+ git_vector_free(&update_heads);+ git_refspec__free(&tagspec);+ git_buf_free(&refname);+ return -1;++}++int git_remote_connected(git_remote *remote)+{+ assert(remote);++ if (!remote->transport || !remote->transport->is_connected)+ return 0;++ /* Ask the transport if it's connected. */+ return remote->transport->is_connected(remote->transport);+}++void git_remote_stop(git_remote *remote)+{+ assert(remote);++ if (remote->transport && remote->transport->cancel)+ remote->transport->cancel(remote->transport);+}++void git_remote_disconnect(git_remote *remote)+{+ assert(remote);++ if (git_remote_connected(remote))+ remote->transport->close(remote->transport);+}++void git_remote_free(git_remote *remote)+{+ if (remote == NULL)+ return;++ if (remote->transport != NULL) {+ git_remote_disconnect(remote);++ remote->transport->free(remote->transport);+ remote->transport = NULL;+ }++ git_vector_free(&remote->refs);++ git_refspec__free(&remote->fetch);+ git_refspec__free(&remote->push);+ git__free(remote->url);+ git__free(remote->pushurl);+ git__free(remote->name);+ git__free(remote);+}++struct cb_data {+ git_vector *list;+ regex_t *preg;+};++static int remote_list_cb(const git_config_entry *entry, void *data_)+{+ struct cb_data *data = (struct cb_data *)data_;+ size_t nmatch = 2;+ regmatch_t pmatch[2];+ const char *name = entry->name;++ if (!regexec(data->preg, name, nmatch, pmatch, 0)) {+ char *remote_name = git__strndup(&name[pmatch[1].rm_so], pmatch[1].rm_eo - pmatch[1].rm_so);+ GITERR_CHECK_ALLOC(remote_name);++ if (git_vector_insert(data->list, remote_name) < 0)+ return -1;+ }++ return 0;+}++int git_remote_list(git_strarray *remotes_list, git_repository *repo)+{+ git_config *cfg;+ git_vector list;+ regex_t preg;+ struct cb_data data;+ int error;++ if (git_repository_config__weakptr(&cfg, repo) < 0)+ return -1;++ if (git_vector_init(&list, 4, NULL) < 0)+ return -1;++ if (regcomp(&preg, "^remote\\.(.*)\\.url$", REG_EXTENDED) < 0) {+ giterr_set(GITERR_OS, "Remote catch regex failed to compile");+ return -1;+ }++ data.list = &list;+ data.preg = &preg;+ error = git_config_foreach(cfg, remote_list_cb, &data);+ regfree(&preg);+ if (error < 0) {+ size_t i;+ char *elem;+ git_vector_foreach(&list, i, elem) {+ git__free(elem);+ }++ git_vector_free(&list);++ /* cb error is converted to GIT_EUSER by git_config_foreach */+ if (error == GIT_EUSER)+ error = -1;++ return error;+ }++ remotes_list->strings = (char **)list.contents;+ remotes_list->count = list.length;++ return 0;+}++void git_remote_check_cert(git_remote *remote, int check)+{+ assert(remote);++ remote->check_cert = check;+}++int git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks)+{+ assert(remote && callbacks);++ GITERR_CHECK_VERSION(callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");++ memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks));++ if (remote->transport && remote->transport->set_callbacks)+ remote->transport->set_callbacks(remote->transport,+ remote->callbacks.progress,+ NULL,+ remote->callbacks.payload);++ return 0;+}++void git_remote_set_cred_acquire_cb(+ git_remote *remote,+ git_cred_acquire_cb cred_acquire_cb,+ void *payload)+{+ assert(remote);++ remote->cred_acquire_cb = cred_acquire_cb;+ remote->cred_acquire_payload = payload;+}++int git_remote_set_transport(git_remote *remote, git_transport *transport)+{+ assert(remote && transport);++ GITERR_CHECK_VERSION(transport, GIT_TRANSPORT_VERSION, "git_transport");++ if (remote->transport) {+ giterr_set(GITERR_NET, "A transport is already bound to this remote");+ return -1;+ }++ remote->transport = transport;+ return 0;+}++const git_transfer_progress* git_remote_stats(git_remote *remote)+{+ assert(remote);+ return &remote->stats;+}++git_remote_autotag_option_t git_remote_autotag(git_remote *remote)+{+ return remote->download_tags;+}++void git_remote_set_autotag(git_remote *remote, git_remote_autotag_option_t value)+{+ remote->download_tags = value;+}++static int rename_remote_config_section(+ git_repository *repo,+ const char *old_name,+ const char *new_name)+{+ git_buf old_section_name = GIT_BUF_INIT,+ new_section_name = GIT_BUF_INIT;+ int error = -1;++ if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0)+ goto cleanup;++ if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0)+ goto cleanup;++ error = git_config_rename_section(+ repo,+ git_buf_cstr(&old_section_name),+ git_buf_cstr(&new_section_name));++cleanup:+ git_buf_free(&old_section_name);+ git_buf_free(&new_section_name);++ return error;+}++struct update_data+{+ git_config *config;+ const char *old_remote_name;+ const char *new_remote_name;+};++static int update_config_entries_cb(+ const git_config_entry *entry,+ void *payload)+{+ struct update_data *data = (struct update_data *)payload;++ if (strcmp(entry->value, data->old_remote_name))+ return 0;++ return git_config_set_string(+ data->config,+ entry->name,+ data->new_remote_name);+}++static int update_branch_remote_config_entry(+ git_repository *repo,+ const char *old_name,+ const char *new_name)+{+ git_config *config;+ struct update_data data;++ if (git_repository_config__weakptr(&config, repo) < 0)+ return -1;++ data.config = config;+ data.old_remote_name = old_name;+ data.new_remote_name = new_name;++ return git_config_foreach_match(+ config,+ "branch\\..+\\.remote",+ update_config_entries_cb, &data);+}++static int rename_cb(const char *ref, void *data)+{+ if (git__prefixcmp(ref, GIT_REFS_REMOTES_DIR))+ return 0;+ + return git_vector_insert((git_vector *)data, git__strdup(ref));+}++static int rename_one_remote_reference(+ git_repository *repo,+ const char *reference_name,+ const char *old_remote_name,+ const char *new_remote_name)+{+ int error = -1;+ git_buf new_name = GIT_BUF_INIT;+ git_reference *reference = NULL;+ git_reference *newref = NULL;++ if (git_buf_printf(+ &new_name,+ GIT_REFS_REMOTES_DIR "%s%s",+ new_remote_name,+ reference_name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0)+ return -1;++ if (git_reference_lookup(&reference, repo, reference_name) < 0)+ goto cleanup;++ error = git_reference_rename(&newref, reference, git_buf_cstr(&new_name), 0);+ git_reference_free(reference);++cleanup:+ git_reference_free(newref);+ git_buf_free(&new_name);+ return error;+}++static int rename_remote_references(+ git_repository *repo,+ const char *old_name,+ const char *new_name)+{+ git_vector refnames;+ int error = -1;+ unsigned int i;+ char *name;++ if (git_vector_init(&refnames, 8, NULL) < 0)+ goto cleanup;++ if (git_reference_foreach(+ repo,+ GIT_REF_LISTALL,+ rename_cb,+ &refnames) < 0)+ goto cleanup;++ git_vector_foreach(&refnames, i, name) {+ if ((error = rename_one_remote_reference(repo, name, old_name, new_name)) < 0)+ goto cleanup;+ }++ error = 0;+cleanup:+ git_vector_foreach(&refnames, i, name) {+ git__free(name);+ }++ git_vector_free(&refnames);+ return error;+}++static int rename_fetch_refspecs(+ git_remote *remote,+ const char *new_name,+ int (*callback)(const char *problematic_refspec, void *payload),+ void *payload)+{+ git_config *config;+ const git_refspec *fetch_refspec;+ git_buf dst_prefix = GIT_BUF_INIT, serialized = GIT_BUF_INIT;+ const char* pos;+ int error = -1;++ fetch_refspec = git_remote_fetchspec(remote);++ /* Is there a refspec to deal with? */+ if (fetch_refspec->src == NULL &&+ fetch_refspec->dst == NULL)+ return 0;++ if (git_refspec__serialize(&serialized, fetch_refspec) < 0)+ goto cleanup;++ /* Is it an in-memory remote? */+ if (!remote->name) {+ error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0;+ goto cleanup;+ }++ if (git_buf_printf(&dst_prefix, ":refs/remotes/%s/", remote->name) < 0)+ goto cleanup;++ pos = strstr(git_buf_cstr(&serialized), git_buf_cstr(&dst_prefix));++ /* Does the dst part of the refspec follow the extected standard format? */+ if (!pos) {+ error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0;+ goto cleanup;+ }++ if (git_buf_splice(+ &serialized,+ pos - git_buf_cstr(&serialized) + strlen(":refs/remotes/"),+ strlen(remote->name), new_name,+ strlen(new_name)) < 0)+ goto cleanup;++ git_refspec__free(&remote->fetch);++ if (git_refspec__parse(&remote->fetch, git_buf_cstr(&serialized), true) < 0)+ goto cleanup;++ if (git_repository_config__weakptr(&config, remote->repo) < 0)+ goto cleanup;++ error = update_config_refspec(config, new_name, &remote->fetch, GIT_DIRECTION_FETCH);++cleanup:+ git_buf_free(&serialized);+ git_buf_free(&dst_prefix);+ return error;+}++int git_remote_rename(+ git_remote *remote,+ const char *new_name,+ git_remote_rename_problem_cb callback,+ void *payload)+{+ int error;++ assert(remote && new_name);++ if (!remote->name) {+ giterr_set(GITERR_INVALID, "Can't rename an in-memory remote.");+ return GIT_EINVALIDSPEC;+ }++ if ((error = ensure_remote_name_is_valid(new_name)) < 0)+ return error;++ if (remote->repo) {+ if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0)+ return error;++ if (!remote->name) {+ if ((error = rename_fetch_refspecs(+ remote,+ new_name,+ callback,+ payload)) < 0)+ return error;++ remote->name = git__strdup(new_name);++ if (!remote->name) return 0;+ return git_remote_save(remote);+ }++ if ((error = rename_remote_config_section(+ remote->repo,+ remote->name,+ new_name)) < 0)+ return error;++ if ((error = update_branch_remote_config_entry(+ remote->repo,+ remote->name,+ new_name)) < 0)+ return error;++ if ((error = rename_remote_references(+ remote->repo,+ remote->name,+ new_name)) < 0)+ return error;++ if ((error = rename_fetch_refspecs(+ remote,+ new_name,+ callback,+ payload)) < 0)+ return error;+ }++ git__free(remote->name);+ remote->name = git__strdup(new_name);++ return 0;+}++int git_remote_update_fetchhead(git_remote *remote)+{+ return remote->update_fetchhead;+}++void git_remote_set_update_fetchhead(git_remote *remote, int value)+{+ remote->update_fetchhead = value;+}++int git_remote_is_valid_name(+ const char *remote_name)+{+ git_buf buf = GIT_BUF_INIT;+ git_refspec refspec;+ int error = -1;++ if (!remote_name || *remote_name == '\0')+ return 0;++ git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", remote_name);+ error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true);++ git_buf_free(&buf);+ git_refspec__free(&refspec);++ giterr_clear();+ return error == 0; }
libgit2/src/remote.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,19 +7,34 @@ #ifndef INCLUDE_remote_h__ #define INCLUDE_remote_h__ +#include "git2/remote.h"+#include "git2/transport.h"+ #include "refspec.h"-#include "transport.h" #include "repository.h" +#define GIT_REMOTE_ORIGIN "origin"+ struct git_remote { char *name; char *url;+ char *pushurl; git_vector refs; struct git_refspec fetch; struct git_refspec push;+ git_cred_acquire_cb cred_acquire_cb;+ void *cred_acquire_payload; git_transport *transport; git_repository *repo;- unsigned int need_pack:1;+ git_remote_callbacks callbacks;+ git_transfer_progress stats;+ unsigned int need_pack;+ git_remote_autotag_option_t download_tags;+ unsigned int check_cert;+ unsigned int update_fetchhead; };++const char* git_remote__urlfordirection(struct git_remote *remote, int direction);+int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url); #endif
+ libgit2/src/repo_template.h view
@@ -0,0 +1,58 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_repo_template_h__+#define INCLUDE_repo_template_h__++#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/"+#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/"++#define GIT_HOOKS_DIR "hooks/"+#define GIT_HOOKS_DIR_MODE 0777++#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample"+#define GIT_HOOKS_README_MODE 0777+#define GIT_HOOKS_README_CONTENT \+"#!/bin/sh\n"\+"#\n"\+"# Place appropriately named executable hook scripts into this directory\n"\+"# to intercept various actions that git takes. See `git help hooks` for\n"\+"# more information.\n"++#define GIT_INFO_DIR "info/"+#define GIT_INFO_DIR_MODE 0777++#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude"+#define GIT_INFO_EXCLUDE_MODE 0666+#define GIT_INFO_EXCLUDE_CONTENT \+"# File patterns to ignore; see `git help ignore` for more information.\n"\+"# Lines that start with '#' are comments.\n"++#define GIT_DESC_FILE "description"+#define GIT_DESC_MODE 0666+#define GIT_DESC_CONTENT \+"Unnamed repository; edit this file 'description' to name the repository.\n"++typedef struct {+ const char *path;+ mode_t mode;+ const char *content;+} repo_template_item;++static repo_template_item repo_template[] = {+ { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */+ { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */+ { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */+ { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */+ { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */+ { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */+ { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT },+ { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT },+ { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT },+ { NULL, 0, NULL }+};++#endif
libgit2/src/repository.c view
@@ -1,957 +1,1737 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */-#include <stdarg.h>-#include <ctype.h>--#include "git2/object.h"--#include "common.h"-#include "repository.h"-#include "commit.h"-#include "tag.h"-#include "blob.h"-#include "fileops.h"-#include "config.h"-#include "refs.h"--#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/"-#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/"--#define GIT_FILE_CONTENT_PREFIX "gitdir:"--#define GIT_BRANCH_MASTER "master"--#define GIT_REPO_VERSION 0--static void drop_odb(git_repository *repo)-{- if (repo->_odb != NULL) {- GIT_REFCOUNT_OWN(repo->_odb, NULL);- git_odb_free(repo->_odb);- repo->_odb = NULL;- }-}--static void drop_config(git_repository *repo)-{- if (repo->_config != NULL) {- GIT_REFCOUNT_OWN(repo->_config, NULL);- git_config_free(repo->_config);- repo->_config = NULL;- }-- git_repository__cvar_cache_clear(repo);-}--static void drop_index(git_repository *repo)-{- if (repo->_index != NULL) {- GIT_REFCOUNT_OWN(repo->_index, NULL);- git_index_free(repo->_index);- repo->_index = NULL;- }-}--void git_repository_free(git_repository *repo)-{- if (repo == NULL)- return;-- git_cache_free(&repo->objects);- git_repository__refcache_free(&repo->references);- git_attr_cache_flush(repo);- git_submodule_config_free(repo);-- git__free(repo->path_repository);- git__free(repo->workdir);-- drop_config(repo);- drop_index(repo);- drop_odb(repo);-- git__free(repo);-}--/*- * Git repository open methods- *- * Open a repository object from its path- */-static bool valid_repository_path(git_buf *repository_path)-{- /* Check OBJECTS_DIR first, since it will generate the longest path name */- if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false)- return false;-- /* Ensure HEAD file exists */- if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false)- return false;-- if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false)- return false;-- return true;-}--static git_repository *repository_alloc(void)-{- git_repository *repo = git__malloc(sizeof(git_repository));- if (!repo)- return NULL;-- memset(repo, 0x0, sizeof(git_repository));-- if (git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free) < 0) {- git__free(repo);- return NULL;- }-- /* set all the entries in the cvar cache to `unset` */- git_repository__cvar_cache_clear(repo);-- return repo;-}--int git_repository_new(git_repository **out)-{- *out = repository_alloc();- return 0;-}--static int load_config_data(git_repository *repo)-{- int is_bare;- git_config *config;-- if (git_repository_config__weakptr(&config, repo) < 0)- return -1;-- if (git_config_get_bool(&is_bare, config, "core.bare") < 0)- return -1; /* FIXME: We assume that a missing core.bare- variable is an error. Is this right? */-- repo->is_bare = is_bare;- return 0;-}--static int load_workdir(git_repository *repo, git_buf *parent_path)-{- int error;- git_config *config;- const char *worktree;- git_buf worktree_buf = GIT_BUF_INIT;-- if (repo->is_bare)- return 0;-- if (git_repository_config__weakptr(&config, repo) < 0)- return -1;-- error = git_config_get_string(&worktree, config, "core.worktree");- if (!error && worktree != NULL)- repo->workdir = git__strdup(worktree);- else if (error != GIT_ENOTFOUND)- return error;- else {- giterr_clear();-- if (parent_path && git_path_isdir(parent_path->ptr))- repo->workdir = git_buf_detach(parent_path);- else {- git_path_dirname_r(&worktree_buf, repo->path_repository);- git_path_to_dir(&worktree_buf);- repo->workdir = git_buf_detach(&worktree_buf);- }- }-- GITERR_CHECK_ALLOC(repo->workdir);-- return 0;-}--/*- * This function returns furthest offset into path where a ceiling dir- * is found, so we can stop processing the path at that point.- *- * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on- * the stack could remove directories name limits, but at the cost of doing- * repeated malloc/frees inside the loop below, so let's not do it now.- */-static int find_ceiling_dir_offset(- const char *path,- const char *ceiling_directories)-{- char buf[GIT_PATH_MAX + 1];- char buf2[GIT_PATH_MAX + 1];- const char *ceil, *sep;- size_t len, max_len = 0, min_len;-- assert(path);-- min_len = (size_t)(git_path_root(path) + 1);-- if (ceiling_directories == NULL || min_len == 0)- return (int)min_len;-- for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) {- for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++);- len = sep - ceil;-- if (len == 0 || len >= sizeof(buf) || git_path_root(ceil) == -1)- continue;-- strncpy(buf, ceil, len);- buf[len] = '\0';-- if (p_realpath(buf, buf2) == NULL)- continue;-- len = strlen(buf2);- if (len > 0 && buf2[len-1] == '/')- buf[--len] = '\0';-- if (!strncmp(path, buf2, len) &&- path[len] == '/' &&- len > max_len)- {- max_len = len;- }- }-- return (int)(max_len <= min_len ? min_len : max_len);-}--/*- * Read the contents of `file_path` and set `path_out` to the repo dir that- * it points to. Before calling, set `path_out` to the base directory that- * should be used if the contents of `file_path` are a relative path.- */-static int read_gitfile(git_buf *path_out, const char *file_path)-{- int error = 0;- git_buf file = GIT_BUF_INIT;- size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX);-- assert(path_out && file_path);-- if (git_futils_readbuffer(&file, file_path) < 0)- return -1;-- git_buf_rtrim(&file);-- if (file.size <= prefix_len ||- memcmp(file.ptr, GIT_FILE_CONTENT_PREFIX, prefix_len) != 0)- {- giterr_set(GITERR_REPOSITORY, "The `.git` file at '%s' is malformed", file_path);- error = -1;- }- else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) {- const char *gitlink = ((const char *)file.ptr) + prefix_len;- while (*gitlink && git__isspace(*gitlink)) gitlink++;- error = git_path_prettify_dir(path_out, gitlink, path_out->ptr);- }-- git_buf_free(&file);- return error;-}--static int find_repo(- git_buf *repo_path,- git_buf *parent_path,- const char *start_path,- uint32_t flags,- const char *ceiling_dirs)-{- int error;- git_buf path = GIT_BUF_INIT;- struct stat st;- dev_t initial_device = 0;- bool try_with_dot_git = false;- int ceiling_offset;-- git_buf_free(repo_path);-- if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0)- return error;-- ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);-- if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)- return error;-- while (!error && !git_buf_len(repo_path)) {- if (p_stat(path.ptr, &st) == 0) {- /* check that we have not crossed device boundaries */- if (initial_device == 0)- initial_device = st.st_dev;- else if (st.st_dev != initial_device &&- (flags & GIT_REPOSITORY_OPEN_CROSS_FS) == 0)- break;-- if (S_ISDIR(st.st_mode)) {- if (valid_repository_path(&path)) {- git_path_to_dir(&path);- git_buf_set(repo_path, path.ptr, path.size);- break;- }- }- else if (S_ISREG(st.st_mode)) {- git_buf repo_link = GIT_BUF_INIT;-- if (!(error = read_gitfile(&repo_link, path.ptr))) {- if (valid_repository_path(&repo_link))- git_buf_swap(repo_path, &repo_link);-- git_buf_free(&repo_link);- break;- }- git_buf_free(&repo_link);- }- }-- /* move up one directory level */- if (git_path_dirname_r(&path, path.ptr) < 0) {- error = -1;- break;- }-- if (try_with_dot_git) {- /* if we tried original dir with and without .git AND either hit- * directory ceiling or NO_SEARCH was requested, then be done.- */- if (path.ptr[ceiling_offset] == '\0' ||- (flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0)- break;- /* otherwise look first for .git item */- error = git_buf_joinpath(&path, path.ptr, DOT_GIT);- }- try_with_dot_git = !try_with_dot_git;- }-- if (!error && parent_path != NULL) {- if (!git_buf_len(repo_path))- git_buf_clear(parent_path);- else {- git_path_dirname_r(parent_path, path.ptr);- git_path_to_dir(parent_path);- }- if (git_buf_oom(parent_path))- return -1;- }-- git_buf_free(&path);-- if (!git_buf_len(repo_path) && !error) {- giterr_set(GITERR_REPOSITORY,- "Could not find repository from '%s'", start_path);- error = GIT_ENOTFOUND;- }-- return error;-}--int git_repository_open_ext(- git_repository **repo_ptr,- const char *start_path,- uint32_t flags,- const char *ceiling_dirs)-{- int error;- git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT;- git_repository *repo;-- *repo_ptr = NULL;-- if ((error = find_repo(&path, &parent, start_path, flags, ceiling_dirs)) < 0)- return error;-- repo = repository_alloc();- GITERR_CHECK_ALLOC(repo);-- repo->path_repository = git_buf_detach(&path);- GITERR_CHECK_ALLOC(repo->path_repository);-- if ((error = load_config_data(repo)) < 0 ||- (error = load_workdir(repo, &parent)) < 0)- {- git_repository_free(repo);- return error;- }-- git_buf_free(&parent);- *repo_ptr = repo;- return 0;-}--int git_repository_open(git_repository **repo_out, const char *path)-{- return git_repository_open_ext(- repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL);-}--int git_repository_discover(- char *repository_path,- size_t size,- const char *start_path,- int across_fs,- const char *ceiling_dirs)-{- git_buf path = GIT_BUF_INIT;- uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0;- int error;-- assert(start_path && repository_path && size > 0);-- *repository_path = '\0';-- if ((error = find_repo(&path, NULL, start_path, flags, ceiling_dirs)) < 0)- return error != GIT_ENOTFOUND ? -1 : error;-- if (size < (size_t)(path.size + 1)) {- giterr_set(GITERR_REPOSITORY,- "The given buffer is too long to store the discovered path");- git_buf_free(&path);- return -1;- }-- /* success: we discovered a repository */- git_buf_copy_cstr(repository_path, size, &path);- git_buf_free(&path);- return 0;-}--static int load_config(- git_config **out,- git_repository *repo,- const char *global_config_path,- const char *system_config_path)-{- git_buf config_path = GIT_BUF_INIT;- git_config *cfg = NULL;-- assert(repo && out);-- if (git_config_new(&cfg) < 0)- return -1;-- if (git_buf_joinpath(- &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0)- goto on_error;-- if (git_config_add_file_ondisk(cfg, config_path.ptr, 3) < 0)- goto on_error;-- git_buf_free(&config_path);-- if (global_config_path != NULL) {- if (git_config_add_file_ondisk(cfg, global_config_path, 2) < 0)- goto on_error;- }-- if (system_config_path != NULL) {- if (git_config_add_file_ondisk(cfg, system_config_path, 1) < 0)- goto on_error;- }-- *out = cfg;- return 0;--on_error:- git_buf_free(&config_path);- git_config_free(cfg);- *out = NULL;- return -1;-}--int git_repository_config__weakptr(git_config **out, git_repository *repo)-{- if (repo->_config == NULL) {- git_buf global_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT;- int res;-- const char *global_config_path = NULL;- const char *system_config_path = NULL;-- if (git_config_find_global_r(&global_buf) == 0)- global_config_path = global_buf.ptr;-- if (git_config_find_system_r(&system_buf) == 0)- system_config_path = system_buf.ptr;-- res = load_config(&repo->_config, repo, global_config_path, system_config_path);-- git_buf_free(&global_buf);- git_buf_free(&system_buf);-- if (res < 0)- return -1;-- GIT_REFCOUNT_OWN(repo->_config, repo);- }-- *out = repo->_config;- return 0;-}--int git_repository_config(git_config **out, git_repository *repo)-{- if (git_repository_config__weakptr(out, repo) < 0)- return -1;-- GIT_REFCOUNT_INC(*out);- return 0;-}--void git_repository_set_config(git_repository *repo, git_config *config)-{- assert(repo && config);-- drop_config(repo);-- repo->_config = config;- GIT_REFCOUNT_OWN(repo->_config, repo);-}--int git_repository_odb__weakptr(git_odb **out, git_repository *repo)-{- assert(repo && out);-- if (repo->_odb == NULL) {- git_buf odb_path = GIT_BUF_INIT;- int res;-- if (git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR) < 0)- return -1;-- res = git_odb_open(&repo->_odb, odb_path.ptr);- git_buf_free(&odb_path); /* done with path */-- if (res < 0)- return -1;-- GIT_REFCOUNT_OWN(repo->_odb, repo);- }-- *out = repo->_odb;- return 0;-}--int git_repository_odb(git_odb **out, git_repository *repo)-{- if (git_repository_odb__weakptr(out, repo) < 0)- return -1;-- GIT_REFCOUNT_INC(*out);- return 0;-}--void git_repository_set_odb(git_repository *repo, git_odb *odb)-{- assert(repo && odb);-- drop_odb(repo);-- repo->_odb = odb;- GIT_REFCOUNT_OWN(repo->_odb, repo);- GIT_REFCOUNT_INC(odb);-}--int git_repository_index__weakptr(git_index **out, git_repository *repo)-{- assert(out && repo);-- if (repo->_index == NULL) {- int res;- git_buf index_path = GIT_BUF_INIT;-- if (git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE) < 0)- return -1;-- res = git_index_open(&repo->_index, index_path.ptr);- git_buf_free(&index_path); /* done with path */-- if (res < 0)- return -1;-- GIT_REFCOUNT_OWN(repo->_index, repo);- }-- *out = repo->_index;- return 0;-}--int git_repository_index(git_index **out, git_repository *repo)-{- if (git_repository_index__weakptr(out, repo) < 0)- return -1;-- GIT_REFCOUNT_INC(*out);- return 0;-}--void git_repository_set_index(git_repository *repo, git_index *index)-{- assert(repo && index);-- drop_index(repo);-- repo->_index = index;- GIT_REFCOUNT_OWN(repo->_index, repo);- GIT_REFCOUNT_INC(index);-}--static int check_repositoryformatversion(git_repository *repo)-{- git_config *config;- int version;-- if (git_repository_config__weakptr(&config, repo) < 0)- return -1;-- if (git_config_get_int32(&version, config, "core.repositoryformatversion") < 0)- return -1;-- if (GIT_REPO_VERSION < version) {- giterr_set(GITERR_REPOSITORY,- "Unsupported repository version %d. Only versions up to %d are supported.",- version, GIT_REPO_VERSION);- return -1;- }-- return 0;-}--static int repo_init_reinit(git_repository **repo_out, const char *repository_path, int is_bare)-{- git_repository *repo = NULL;-- GIT_UNUSED(is_bare);-- if (git_repository_open(&repo, repository_path) < 0)- return -1;-- if (check_repositoryformatversion(repo) < 0) {- git_repository_free(repo);- return -1;- }-- /* TODO: reinitialize the templates */-- *repo_out = repo;- return 0;-}--static int repo_init_createhead(const char *git_dir)-{- git_buf ref_path = GIT_BUF_INIT;- git_filebuf ref = GIT_FILEBUF_INIT;-- if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 ||- git_filebuf_open(&ref, ref_path.ptr, 0) < 0 ||- git_filebuf_printf(&ref, "ref: refs/heads/master\n") < 0 ||- git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0)- return -1;-- git_buf_free(&ref_path);- return 0;-}--static int repo_init_config(const char *git_dir, int is_bare)-{- git_buf cfg_path = GIT_BUF_INIT;- git_config *config = NULL;--#define SET_REPO_CONFIG(type, name, val) {\- if (git_config_set_##type(config, name, val) < 0) { \- git_buf_free(&cfg_path); \- git_config_free(config); \- return -1; } \-}-- if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0)- return -1;-- if (git_config_open_ondisk(&config, cfg_path.ptr) < 0) {- git_buf_free(&cfg_path);- return -1;- }-- SET_REPO_CONFIG(bool, "core.bare", is_bare);- SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION);- /* TODO: what other defaults? */-- git_buf_free(&cfg_path);- git_config_free(config);- return 0;-}--#define GIT_HOOKS_DIR "hooks/"-#define GIT_HOOKS_DIR_MODE 0755--#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample"-#define GIT_HOOKS_README_MODE 0755-#define GIT_HOOKS_README_CONTENT \-"#!/bin/sh\n"\-"#\n"\-"# Place appropriately named executable hook scripts into this directory\n"\-"# to intercept various actions that git takes. See `git help hooks` for\n"\-"# more information.\n"--#define GIT_INFO_DIR "info/"-#define GIT_INFO_DIR_MODE 0755--#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude"-#define GIT_INFO_EXCLUDE_MODE 0644-#define GIT_INFO_EXCLUDE_CONTENT \-"# File patterns to ignore; see `git help ignore` for more information.\n"\-"# Lines that start with '#' are comments.\n"--#define GIT_DESC_FILE "description"-#define GIT_DESC_MODE 0644-#define GIT_DESC_CONTENT "Unnamed repository; edit this file 'description' to name the repository.\n"--static int repo_write_template(- const char *git_dir, const char *file, mode_t mode, const char *content)-{- git_buf path = GIT_BUF_INIT;- int fd, error = 0;-- if (git_buf_joinpath(&path, git_dir, file) < 0)- return -1;-- fd = p_open(git_buf_cstr(&path), O_WRONLY | O_CREAT | O_EXCL, mode);-- if (fd >= 0) {- error = p_write(fd, content, strlen(content));-- p_close(fd);- }- else if (errno != EEXIST)- error = fd;-- git_buf_free(&path);-- if (error)- giterr_set(GITERR_OS,- "Failed to initialize repository with template '%s'", file);-- return error;-}--static int repo_init_structure(const char *git_dir, int is_bare)-{- int i;- struct { const char *dir; mode_t mode; } dirs[] = {- { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/info/' */- { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/pack/' */- { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/heads/' */- { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/tags/' */- { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE }, /* '/hooks/' */- { GIT_INFO_DIR, GIT_INFO_DIR_MODE }, /* '/info/' */- { NULL, 0 }- };- struct { const char *file; mode_t mode; const char *content; } tmpl[] = {- { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT },- { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT },- { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT },- { NULL, 0, NULL }- };-- /* Make the base directory */- if (git_futils_mkdir_r(git_dir, NULL, is_bare ? GIT_BARE_DIR_MODE : GIT_DIR_MODE) < 0)- return -1;-- /* Hides the ".git" directory */- if (!is_bare) {-#ifdef GIT_WIN32- if (p_hide_directory__w32(git_dir) < 0) {- giterr_set(GITERR_REPOSITORY,- "Failed to mark Git repository folder as hidden");- return -1;- }-#endif- }-- /* Make subdirectories as needed */- for (i = 0; dirs[i].dir != NULL; ++i) {- if (git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode) < 0)- return -1;- }-- /* Make template files as needed */- for (i = 0; tmpl[i].file != NULL; ++i) {- if (repo_write_template(- git_dir, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0)- return -1;- }-- return 0;-}--int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare)-{- git_buf repository_path = GIT_BUF_INIT;-- assert(repo_out && path);-- if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0)- return -1;-- if (git_path_isdir(repository_path.ptr) == true) {- if (valid_repository_path(&repository_path) == true) {- int res = repo_init_reinit(repo_out, repository_path.ptr, is_bare);- git_buf_free(&repository_path);- return res;- }- }-- if (repo_init_structure(repository_path.ptr, is_bare) < 0 ||- repo_init_config(repository_path.ptr, is_bare) < 0 || - repo_init_createhead(repository_path.ptr) < 0 ||- git_repository_open(repo_out, repository_path.ptr) < 0) {- git_buf_free(&repository_path);- return -1;- }-- git_buf_free(&repository_path);- return 0;-}--int git_repository_head_detached(git_repository *repo)-{- git_reference *ref;- git_odb *odb = NULL;- int exists;-- if (git_repository_odb__weakptr(&odb, repo) < 0)- return -1;-- if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)- return -1;-- if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {- git_reference_free(ref);- return 0;- }-- exists = git_odb_exists(odb, git_reference_oid(ref));-- git_reference_free(ref);- return exists;-}--int git_repository_head(git_reference **head_out, git_repository *repo)-{- return git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1);-}--int git_repository_head_orphan(git_repository *repo)-{- git_reference *ref = NULL;- int error;-- error = git_repository_head(&ref, repo);- git_reference_free(ref);-- if (error == GIT_ENOTFOUND)- return 1;-- if (error < 0)- return -1;-- return 0;-}--int git_repository_is_empty(git_repository *repo)-{- git_reference *head = NULL, *branch = NULL;- int error;-- if (git_reference_lookup(&head, repo, "HEAD") < 0)- return -1;-- if (git_reference_type(head) != GIT_REF_SYMBOLIC) {- git_reference_free(head);- return 0;- }-- if (strcmp(git_reference_target(head), "refs/heads/master") != 0) {- git_reference_free(head);- return 0;- }-- error = git_reference_resolve(&branch, head);-- git_reference_free(head);- git_reference_free(branch);-- if (error == GIT_ENOTFOUND)- return 1;-- if (error < 0)- return -1;-- return 0;-}--const char *git_repository_path(git_repository *repo)-{- assert(repo);- return repo->path_repository;-}--const char *git_repository_workdir(git_repository *repo)-{- assert(repo);-- if (repo->is_bare)- return NULL;-- return repo->workdir;-}--int git_repository_set_workdir(git_repository *repo, const char *workdir)-{- git_buf path = GIT_BUF_INIT;-- assert(repo && workdir);-- if (git_path_prettify_dir(&path, workdir, NULL) < 0)- return -1;-- git__free(repo->workdir);-- repo->workdir = git_buf_detach(&path);- repo->is_bare = 0;- return 0;-}--int git_repository_is_bare(git_repository *repo)-{- assert(repo);- return repo->is_bare;-}--int git_repository_head_tree(git_tree **tree, git_repository *repo)-{- git_oid head_oid;- git_object *obj = NULL;-- if (git_reference_name_to_oid(&head_oid, repo, GIT_HEAD_FILE) < 0) {- /* cannot resolve HEAD - probably brand new repo */- giterr_clear();- *tree = NULL;- return 0;- }-- if (git_object_lookup(&obj, repo, &head_oid, GIT_OBJ_ANY) < 0 ||- git_object__resolve_to_type(&obj, GIT_OBJ_TREE) < 0)- return -1;-- *tree = (git_tree *)obj;- return 0;+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#include <stdarg.h>+#include <ctype.h>++#include "git2/object.h"+#include "git2/refdb.h"++#include "common.h"+#include "repository.h"+#include "commit.h"+#include "tag.h"+#include "blob.h"+#include "fileops.h"+#include "config.h"+#include "refs.h"+#include "filter.h"+#include "odb.h"+#include "remote.h"+#include "merge.h"++#define GIT_FILE_CONTENT_PREFIX "gitdir:"++#define GIT_BRANCH_MASTER "master"++#define GIT_REPO_VERSION 0++#define GIT_TEMPLATE_DIR "/usr/share/git-core/templates"++static void drop_odb(git_repository *repo)+{+ if (repo->_odb != NULL) {+ GIT_REFCOUNT_OWN(repo->_odb, NULL);+ git_odb_free(repo->_odb);+ repo->_odb = NULL;+ }+}++static void drop_refdb(git_repository *repo)+{+ if (repo->_refdb != NULL) {+ GIT_REFCOUNT_OWN(repo->_refdb, NULL);+ git_refdb_free(repo->_refdb);+ repo->_refdb = NULL;+ }+}++static void drop_config(git_repository *repo)+{+ if (repo->_config != NULL) {+ GIT_REFCOUNT_OWN(repo->_config, NULL);+ git_config_free(repo->_config);+ repo->_config = NULL;+ }++ git_repository__cvar_cache_clear(repo);+}++static void drop_index(git_repository *repo)+{+ if (repo->_index != NULL) {+ GIT_REFCOUNT_OWN(repo->_index, NULL);+ git_index_free(repo->_index);+ repo->_index = NULL;+ }+}++void git_repository_free(git_repository *repo)+{+ if (repo == NULL)+ return;++ git_cache_free(&repo->objects);+ git_attr_cache_flush(repo);+ git_submodule_config_free(repo);++ git__free(repo->path_repository);+ git__free(repo->workdir);++ drop_config(repo);+ drop_index(repo);+ drop_odb(repo);+ drop_refdb(repo);++ git__free(repo);+}++/*+ * Git repository open methods+ *+ * Open a repository object from its path+ */+static bool valid_repository_path(git_buf *repository_path)+{+ /* Check OBJECTS_DIR first, since it will generate the longest path name */+ if (git_path_contains_dir(repository_path, GIT_OBJECTS_DIR) == false)+ return false;++ /* Ensure HEAD file exists */+ if (git_path_contains_file(repository_path, GIT_HEAD_FILE) == false)+ return false;++ if (git_path_contains_dir(repository_path, GIT_REFS_DIR) == false)+ return false;++ return true;+}++static git_repository *repository_alloc(void)+{+ git_repository *repo = git__malloc(sizeof(git_repository));+ if (!repo)+ return NULL;++ memset(repo, 0x0, sizeof(git_repository));++ if (git_cache_init(&repo->objects, GIT_DEFAULT_CACHE_SIZE, &git_object__free) < 0) {+ git__free(repo);+ return NULL;+ }++ /* set all the entries in the cvar cache to `unset` */+ git_repository__cvar_cache_clear(repo);++ return repo;+}++int git_repository_new(git_repository **out)+{+ *out = repository_alloc();+ return 0;+}++static int load_config_data(git_repository *repo)+{+ int is_bare;+ git_config *config;++ if (git_repository_config__weakptr(&config, repo) < 0)+ return -1;++ /* Try to figure out if it's bare, default to non-bare if it's not set */+ if (git_config_get_bool(&is_bare, config, "core.bare") < 0)+ repo->is_bare = 0;+ else+ repo->is_bare = is_bare;++ return 0;+}++static int load_workdir(git_repository *repo, git_buf *parent_path)+{+ int error;+ git_config *config;+ const char *worktree;+ git_buf worktree_buf = GIT_BUF_INIT;++ if (repo->is_bare)+ return 0;++ if (git_repository_config__weakptr(&config, repo) < 0)+ return -1;++ error = git_config_get_string(&worktree, config, "core.worktree");+ if (!error && worktree != NULL) {+ error = git_path_prettify_dir(+ &worktree_buf, worktree, repo->path_repository);+ if (error < 0)+ return error;+ repo->workdir = git_buf_detach(&worktree_buf);+ }+ else if (error != GIT_ENOTFOUND)+ return error;+ else {+ giterr_clear();++ if (parent_path && git_path_isdir(parent_path->ptr))+ repo->workdir = git_buf_detach(parent_path);+ else {+ git_path_dirname_r(&worktree_buf, repo->path_repository);+ git_path_to_dir(&worktree_buf);+ repo->workdir = git_buf_detach(&worktree_buf);+ }+ }++ GITERR_CHECK_ALLOC(repo->workdir);++ return 0;+}++/*+ * This function returns furthest offset into path where a ceiling dir+ * is found, so we can stop processing the path at that point.+ *+ * Note: converting this to use git_bufs instead of GIT_PATH_MAX buffers on+ * the stack could remove directories name limits, but at the cost of doing+ * repeated malloc/frees inside the loop below, so let's not do it now.+ */+static int find_ceiling_dir_offset(+ const char *path,+ const char *ceiling_directories)+{+ char buf[GIT_PATH_MAX + 1];+ char buf2[GIT_PATH_MAX + 1];+ const char *ceil, *sep;+ size_t len, max_len = 0, min_len;++ assert(path);++ min_len = (size_t)(git_path_root(path) + 1);++ if (ceiling_directories == NULL || min_len == 0)+ return (int)min_len;++ for (sep = ceil = ceiling_directories; *sep; ceil = sep + 1) {+ for (sep = ceil; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++);+ len = sep - ceil;++ if (len == 0 || len >= sizeof(buf) || git_path_root(ceil) == -1)+ continue;++ strncpy(buf, ceil, len);+ buf[len] = '\0';++ if (p_realpath(buf, buf2) == NULL)+ continue;++ len = strlen(buf2);+ if (len > 0 && buf2[len-1] == '/')+ buf[--len] = '\0';++ if (!strncmp(path, buf2, len) &&+ path[len] == '/' &&+ len > max_len)+ {+ max_len = len;+ }+ }++ return (int)(max_len <= min_len ? min_len : max_len);+}++/*+ * Read the contents of `file_path` and set `path_out` to the repo dir that+ * it points to. Before calling, set `path_out` to the base directory that+ * should be used if the contents of `file_path` are a relative path.+ */+static int read_gitfile(git_buf *path_out, const char *file_path)+{+ int error = 0;+ git_buf file = GIT_BUF_INIT;+ size_t prefix_len = strlen(GIT_FILE_CONTENT_PREFIX);++ assert(path_out && file_path);++ if (git_futils_readbuffer(&file, file_path) < 0)+ return -1;++ git_buf_rtrim(&file);++ if (git_buf_len(&file) <= prefix_len ||+ memcmp(git_buf_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0)+ {+ giterr_set(GITERR_REPOSITORY, "The `.git` file at '%s' is malformed", file_path);+ error = -1;+ }+ else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) {+ const char *gitlink = git_buf_cstr(&file) + prefix_len;+ while (*gitlink && git__isspace(*gitlink)) gitlink++;+ error = git_path_prettify_dir(+ path_out, gitlink, git_buf_cstr(path_out));+ }++ git_buf_free(&file);+ return error;+}++static int find_repo(+ git_buf *repo_path,+ git_buf *parent_path,+ const char *start_path,+ uint32_t flags,+ const char *ceiling_dirs)+{+ int error;+ git_buf path = GIT_BUF_INIT;+ struct stat st;+ dev_t initial_device = 0;+ bool try_with_dot_git = false;+ int ceiling_offset;++ git_buf_free(repo_path);++ if ((error = git_path_prettify_dir(&path, start_path, NULL)) < 0)+ return error;++ ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs);++ if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0)+ return error;++ while (!error && !git_buf_len(repo_path)) {+ if (p_stat(path.ptr, &st) == 0) {+ /* check that we have not crossed device boundaries */+ if (initial_device == 0)+ initial_device = st.st_dev;+ else if (st.st_dev != initial_device &&+ (flags & GIT_REPOSITORY_OPEN_CROSS_FS) == 0)+ break;++ if (S_ISDIR(st.st_mode)) {+ if (valid_repository_path(&path)) {+ git_path_to_dir(&path);+ git_buf_set(repo_path, path.ptr, path.size);+ break;+ }+ }+ else if (S_ISREG(st.st_mode)) {+ git_buf repo_link = GIT_BUF_INIT;++ if (!(error = read_gitfile(&repo_link, path.ptr))) {+ if (valid_repository_path(&repo_link))+ git_buf_swap(repo_path, &repo_link);++ git_buf_free(&repo_link);+ break;+ }+ git_buf_free(&repo_link);+ }+ }++ /* move up one directory level */+ if (git_path_dirname_r(&path, path.ptr) < 0) {+ error = -1;+ break;+ }++ if (try_with_dot_git) {+ /* if we tried original dir with and without .git AND either hit+ * directory ceiling or NO_SEARCH was requested, then be done.+ */+ if (path.ptr[ceiling_offset] == '\0' ||+ (flags & GIT_REPOSITORY_OPEN_NO_SEARCH) != 0)+ break;+ /* otherwise look first for .git item */+ error = git_buf_joinpath(&path, path.ptr, DOT_GIT);+ }+ try_with_dot_git = !try_with_dot_git;+ }++ if (!error && parent_path != NULL) {+ if (!git_buf_len(repo_path))+ git_buf_clear(parent_path);+ else {+ git_path_dirname_r(parent_path, path.ptr);+ git_path_to_dir(parent_path);+ }+ if (git_buf_oom(parent_path))+ return -1;+ }++ git_buf_free(&path);++ if (!git_buf_len(repo_path) && !error) {+ giterr_set(GITERR_REPOSITORY,+ "Could not find repository from '%s'", start_path);+ error = GIT_ENOTFOUND;+ }++ return error;+}++int git_repository_open_ext(+ git_repository **repo_ptr,+ const char *start_path,+ unsigned int flags,+ const char *ceiling_dirs)+{+ int error;+ git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT;+ git_repository *repo;++ if (repo_ptr)+ *repo_ptr = NULL;++ error = find_repo(&path, &parent, start_path, flags, ceiling_dirs);+ if (error < 0 || !repo_ptr)+ return error;++ repo = repository_alloc();+ GITERR_CHECK_ALLOC(repo);++ repo->path_repository = git_buf_detach(&path);+ GITERR_CHECK_ALLOC(repo->path_repository);++ if ((error = load_config_data(repo)) < 0 ||+ (error = load_workdir(repo, &parent)) < 0)+ {+ git_repository_free(repo);+ return error;+ }++ git_buf_free(&parent);+ *repo_ptr = repo;+ return 0;+}++int git_repository_open(git_repository **repo_out, const char *path)+{+ return git_repository_open_ext(+ repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL);+}++int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb)+{+ git_repository *repo;++ repo = repository_alloc();+ GITERR_CHECK_ALLOC(repo);++ git_repository_set_odb(repo, odb);+ *repo_out = repo;++ return 0;+}++int git_repository_discover(+ char *repository_path,+ size_t size,+ const char *start_path,+ int across_fs,+ const char *ceiling_dirs)+{+ git_buf path = GIT_BUF_INIT;+ uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0;+ int error;++ assert(start_path && repository_path && size > 0);++ *repository_path = '\0';++ if ((error = find_repo(&path, NULL, start_path, flags, ceiling_dirs)) < 0)+ return error != GIT_ENOTFOUND ? -1 : error;++ if (size < (size_t)(path.size + 1)) {+ giterr_set(GITERR_REPOSITORY,+ "The given buffer is too small to store the discovered path");+ git_buf_free(&path);+ return -1;+ }++ /* success: we discovered a repository */+ git_buf_copy_cstr(repository_path, size, &path);+ git_buf_free(&path);+ return 0;+}++static int load_config(+ git_config **out,+ git_repository *repo,+ const char *global_config_path,+ const char *xdg_config_path,+ const char *system_config_path)+{+ int error;+ git_buf config_path = GIT_BUF_INIT;+ git_config *cfg = NULL;++ assert(repo && out);++ if ((error = git_config_new(&cfg)) < 0)+ return error;++ error = git_buf_joinpath(+ &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO);+ if (error < 0)+ goto on_error;++ if ((error = git_config_add_file_ondisk(+ cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0 &&+ error != GIT_ENOTFOUND)+ goto on_error;++ git_buf_free(&config_path);++ if (global_config_path != NULL &&+ (error = git_config_add_file_ondisk(+ cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0)) < 0 &&+ error != GIT_ENOTFOUND)+ goto on_error;++ if (xdg_config_path != NULL &&+ (error = git_config_add_file_ondisk(+ cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0)) < 0 &&+ error != GIT_ENOTFOUND)+ goto on_error;++ if (system_config_path != NULL &&+ (error = git_config_add_file_ondisk(+ cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0)) < 0 &&+ error != GIT_ENOTFOUND)+ goto on_error;++ giterr_clear(); /* clear any lingering ENOTFOUND errors */++ *out = cfg;+ return 0;++on_error:+ git_buf_free(&config_path);+ git_config_free(cfg);+ *out = NULL;+ return error;+}++int git_repository_config__weakptr(git_config **out, git_repository *repo)+{+ if (repo->_config == NULL) {+ git_buf global_buf = GIT_BUF_INIT, xdg_buf = GIT_BUF_INIT, system_buf = GIT_BUF_INIT;+ int res;++ const char *global_config_path = NULL;+ const char *xdg_config_path = NULL;+ const char *system_config_path = NULL;++ if (git_config_find_global_r(&global_buf) == 0)+ global_config_path = global_buf.ptr;++ if (git_config_find_xdg_r(&xdg_buf) == 0)+ xdg_config_path = xdg_buf.ptr;++ if (git_config_find_system_r(&system_buf) == 0)+ system_config_path = system_buf.ptr;++ res = load_config(&repo->_config, repo, global_config_path, xdg_config_path, system_config_path);++ git_buf_free(&global_buf);+ git_buf_free(&xdg_buf);+ git_buf_free(&system_buf);++ if (res < 0)+ return -1;++ GIT_REFCOUNT_OWN(repo->_config, repo);+ }++ *out = repo->_config;+ return 0;+}++int git_repository_config(git_config **out, git_repository *repo)+{+ if (git_repository_config__weakptr(out, repo) < 0)+ return -1;++ GIT_REFCOUNT_INC(*out);+ return 0;+}++void git_repository_set_config(git_repository *repo, git_config *config)+{+ assert(repo && config);++ drop_config(repo);++ repo->_config = config;+ GIT_REFCOUNT_OWN(repo->_config, repo);+ GIT_REFCOUNT_INC(repo->_config);+}++int git_repository_odb__weakptr(git_odb **out, git_repository *repo)+{+ assert(repo && out);++ if (repo->_odb == NULL) {+ git_buf odb_path = GIT_BUF_INIT;+ int res;++ if (git_buf_joinpath(&odb_path, repo->path_repository, GIT_OBJECTS_DIR) < 0)+ return -1;++ res = git_odb_open(&repo->_odb, odb_path.ptr);+ git_buf_free(&odb_path); /* done with path */++ if (res < 0)+ return -1;++ GIT_REFCOUNT_OWN(repo->_odb, repo);+ }++ *out = repo->_odb;+ return 0;+}++int git_repository_odb(git_odb **out, git_repository *repo)+{+ if (git_repository_odb__weakptr(out, repo) < 0)+ return -1;++ GIT_REFCOUNT_INC(*out);+ return 0;+}++void git_repository_set_odb(git_repository *repo, git_odb *odb)+{+ assert(repo && odb);++ drop_odb(repo);++ repo->_odb = odb;+ GIT_REFCOUNT_OWN(repo->_odb, repo);+ GIT_REFCOUNT_INC(odb);+}++int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo)+{+ assert(out && repo);++ if (repo->_refdb == NULL) {+ int res;++ res = git_refdb_open(&repo->_refdb, repo);++ if (res < 0)+ return -1;++ GIT_REFCOUNT_OWN(repo->_refdb, repo);+ }++ *out = repo->_refdb;+ return 0;+}++int git_repository_refdb(git_refdb **out, git_repository *repo)+{+ if (git_repository_refdb__weakptr(out, repo) < 0)+ return -1;++ GIT_REFCOUNT_INC(*out);+ return 0;+}++void git_repository_set_refdb(git_repository *repo, git_refdb *refdb)+{+ assert (repo && refdb);++ drop_refdb(repo);++ repo->_refdb = refdb;+ GIT_REFCOUNT_OWN(repo->_refdb, repo);+ GIT_REFCOUNT_INC(refdb);+}++int git_repository_index__weakptr(git_index **out, git_repository *repo)+{+ assert(out && repo);++ if (repo->_index == NULL) {+ int res;+ git_buf index_path = GIT_BUF_INIT;++ if (git_buf_joinpath(&index_path, repo->path_repository, GIT_INDEX_FILE) < 0)+ return -1;++ res = git_index_open(&repo->_index, index_path.ptr);+ git_buf_free(&index_path); /* done with path */++ if (res < 0)+ return -1;++ GIT_REFCOUNT_OWN(repo->_index, repo);++ if (git_index_set_caps(repo->_index, GIT_INDEXCAP_FROM_OWNER) < 0)+ return -1;+ }++ *out = repo->_index;+ return 0;+}++int git_repository_index(git_index **out, git_repository *repo)+{+ if (git_repository_index__weakptr(out, repo) < 0)+ return -1;++ GIT_REFCOUNT_INC(*out);+ return 0;+}++void git_repository_set_index(git_repository *repo, git_index *index)+{+ assert(repo && index);++ drop_index(repo);++ repo->_index = index;+ GIT_REFCOUNT_OWN(repo->_index, repo);+ GIT_REFCOUNT_INC(index);+}++static int check_repositoryformatversion(git_config *config)+{+ int version;++ if (git_config_get_int32(&version, config, "core.repositoryformatversion") < 0)+ return -1;++ if (GIT_REPO_VERSION < version) {+ giterr_set(GITERR_REPOSITORY,+ "Unsupported repository version %d. Only versions up to %d are supported.",+ version, GIT_REPO_VERSION);+ return -1;+ }++ return 0;+}++static int repo_init_create_head(const char *git_dir, const char *ref_name)+{+ git_buf ref_path = GIT_BUF_INIT;+ git_filebuf ref = GIT_FILEBUF_INIT;+ const char *fmt;++ if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 ||+ git_filebuf_open(&ref, ref_path.ptr, 0) < 0)+ goto fail;++ if (!ref_name)+ ref_name = GIT_BRANCH_MASTER;++ if (git__prefixcmp(ref_name, GIT_REFS_DIR) == 0)+ fmt = "ref: %s\n";+ else+ fmt = "ref: " GIT_REFS_HEADS_DIR "%s\n";++ if (git_filebuf_printf(&ref, fmt, ref_name) < 0 ||+ git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0)+ goto fail;++ git_buf_free(&ref_path);+ return 0;++fail:+ git_buf_free(&ref_path);+ git_filebuf_cleanup(&ref);+ return -1;+}++static bool is_chmod_supported(const char *file_path)+{+ struct stat st1, st2;+ static int _is_supported = -1;++ if (_is_supported > -1)+ return _is_supported;++ if (p_stat(file_path, &st1) < 0)+ return false;++ if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0)+ return false;++ if (p_stat(file_path, &st2) < 0)+ return false;++ _is_supported = (st1.st_mode != st2.st_mode);++ return _is_supported;+}++static bool is_filesystem_case_insensitive(const char *gitdir_path)+{+ git_buf path = GIT_BUF_INIT;+ static int _is_insensitive = -1;++ if (_is_insensitive > -1)+ return _is_insensitive;++ if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0)+ goto cleanup;++ _is_insensitive = git_path_exists(git_buf_cstr(&path));++cleanup:+ git_buf_free(&path);+ return _is_insensitive;+}++static bool are_symlinks_supported(const char *wd_path)+{+ git_buf path = GIT_BUF_INIT;+ int fd;+ struct stat st;+ static int _symlinks_supported = -1;++ if (_symlinks_supported > -1)+ return _symlinks_supported;++ if ((fd = git_futils_mktmp(&path, wd_path)) < 0 ||+ p_close(fd) < 0 ||+ p_unlink(path.ptr) < 0 ||+ p_symlink("testing", path.ptr) < 0 ||+ p_lstat(path.ptr, &st) < 0)+ _symlinks_supported = false;+ else+ _symlinks_supported = (S_ISLNK(st.st_mode) != 0);++ (void)p_unlink(path.ptr);+ git_buf_free(&path);++ return _symlinks_supported;+}++static int create_empty_file(const char *path, mode_t mode)+{+ int fd;++ if ((fd = p_creat(path, mode)) < 0) {+ giterr_set(GITERR_OS, "Error while creating '%s'", path);+ return -1;+ }++ if (p_close(fd) < 0) {+ giterr_set(GITERR_OS, "Error while closing '%s'", path);+ return -1;+ }++ return 0;+}++static int repo_init_config(+ const char *repo_dir,+ const char *work_dir,+ git_repository_init_options *opts)+{+ int error = 0;+ git_buf cfg_path = GIT_BUF_INIT;+ git_config *config = NULL;++#define SET_REPO_CONFIG(TYPE, NAME, VAL) do {\+ if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \+ goto cleanup; } while (0)++ if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)+ return -1;++ if (!git_path_isfile(git_buf_cstr(&cfg_path)) &&+ create_empty_file(git_buf_cstr(&cfg_path), GIT_CONFIG_FILE_MODE) < 0) {+ git_buf_free(&cfg_path);+ return -1;+ }++ if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) {+ git_buf_free(&cfg_path);+ return -1;+ }++ if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0 &&+ (error = check_repositoryformatversion(config)) < 0)+ goto cleanup;++ SET_REPO_CONFIG(+ bool, "core.bare", (opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);+ SET_REPO_CONFIG(+ int32, "core.repositoryformatversion", GIT_REPO_VERSION);+ SET_REPO_CONFIG(+ bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path)));++ if (!(opts->flags & GIT_REPOSITORY_INIT_BARE)) {+ SET_REPO_CONFIG(bool, "core.logallrefupdates", true);++ if (!are_symlinks_supported(work_dir))+ SET_REPO_CONFIG(bool, "core.symlinks", false);++ if (!(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD)) {+ SET_REPO_CONFIG(string, "core.worktree", work_dir);+ }+ else if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0) {+ if (git_config_delete_entry(config, "core.worktree") < 0)+ giterr_clear();+ }+ } else {+ if (!are_symlinks_supported(repo_dir))+ SET_REPO_CONFIG(bool, "core.symlinks", false);+ }++ if (!(opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) &&+ is_filesystem_case_insensitive(repo_dir))+ SET_REPO_CONFIG(bool, "core.ignorecase", true);++ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) {+ SET_REPO_CONFIG(int32, "core.sharedrepository", 1);+ SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);+ }+ else if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) {+ SET_REPO_CONFIG(int32, "core.sharedrepository", 2);+ SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true);+ }++cleanup:+ git_buf_free(&cfg_path);+ git_config_free(config);++ return error;+}++static int repo_write_template(+ const char *git_dir,+ bool allow_overwrite,+ const char *file,+ mode_t mode,+ bool hidden,+ const char *content)+{+ git_buf path = GIT_BUF_INIT;+ int fd, error = 0, flags;++ if (git_buf_joinpath(&path, git_dir, file) < 0)+ return -1;++ if (allow_overwrite)+ flags = O_WRONLY | O_CREAT | O_TRUNC;+ else+ flags = O_WRONLY | O_CREAT | O_EXCL;++ fd = p_open(git_buf_cstr(&path), flags, mode);++ if (fd >= 0) {+ error = p_write(fd, content, strlen(content));++ p_close(fd);+ }+ else if (errno != EEXIST)+ error = fd;++#ifdef GIT_WIN32+ if (!error && hidden) {+ if (p_hide_directory__w32(path.ptr) < 0)+ error = -1;+ }+#else+ GIT_UNUSED(hidden);+#endif++ git_buf_free(&path);++ if (error)+ giterr_set(GITERR_OS,+ "Failed to initialize repository with template '%s'", file);++ return error;+}++static int repo_write_gitlink(+ const char *in_dir, const char *to_repo)+{+ int error;+ git_buf buf = GIT_BUF_INIT;+ struct stat st;++ git_path_dirname_r(&buf, to_repo);+ git_path_to_dir(&buf);+ if (git_buf_oom(&buf))+ return -1;++ /* don't write gitlink to natural workdir */+ if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 &&+ strcmp(in_dir, buf.ptr) == 0)+ {+ error = GIT_PASSTHROUGH;+ goto cleanup;+ }++ if ((error = git_buf_joinpath(&buf, in_dir, DOT_GIT)) < 0)+ goto cleanup;++ if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) {+ giterr_set(GITERR_REPOSITORY,+ "Cannot overwrite gitlink file into path '%s'", in_dir);+ error = GIT_EEXISTS;+ goto cleanup;+ }++ git_buf_clear(&buf);++ error = git_buf_printf(&buf, "%s %s", GIT_FILE_CONTENT_PREFIX, to_repo);++ if (!error)+ error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr);++cleanup:+ git_buf_free(&buf);+ return error;+}++static mode_t pick_dir_mode(git_repository_init_options *opts)+{+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK)+ return 0777;+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP)+ return (0775 | S_ISGID);+ if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL)+ return (0777 | S_ISGID);+ return opts->mode;+}++#include "repo_template.h"++static int repo_init_structure(+ const char *repo_dir,+ const char *work_dir,+ git_repository_init_options *opts)+{+ int error = 0;+ repo_template_item *tpl;+ bool external_tpl =+ ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0);+ mode_t dmode = pick_dir_mode(opts);++ /* Hide the ".git" directory */+#ifdef GIT_WIN32+ if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) {+ if (p_hide_directory__w32(repo_dir) < 0) {+ giterr_set(GITERR_REPOSITORY,+ "Failed to mark Git repository folder as hidden");+ return -1;+ }+ }+#endif++ /* Create the .git gitlink if appropriate */+ if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 &&+ (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0)+ {+ if (repo_write_gitlink(work_dir, repo_dir) < 0)+ return -1;+ }++ /* Copy external template if requested */+ if (external_tpl) {+ git_config *cfg;+ const char *tdir;++ if (opts->template_path)+ tdir = opts->template_path;+ else if ((error = git_config_open_default(&cfg)) < 0)+ return error;+ else {+ error = git_config_get_string(&tdir, cfg, "init.templatedir");++ git_config_free(cfg);++ if (error && error != GIT_ENOTFOUND)+ return error;++ giterr_clear();+ tdir = GIT_TEMPLATE_DIR;+ }++ error = git_futils_cp_r(tdir, repo_dir,+ GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS |+ GIT_CPDIR_SIMPLE_TO_MODE, dmode);++ if (error < 0) {+ if (strcmp(tdir, GIT_TEMPLATE_DIR) != 0)+ return error;++ /* if template was default, ignore error and use internal */+ giterr_clear();+ external_tpl = false;+ error = 0;+ }+ }++ /* Copy internal template+ * - always ensure existence of dirs+ * - only create files if no external template was specified+ */+ for (tpl = repo_template; !error && tpl->path; ++tpl) {+ if (!tpl->content)+ error = git_futils_mkdir(+ tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD);+ else if (!external_tpl) {+ const char *content = tpl->content;++ if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0)+ content = opts->description;++ error = repo_write_template(+ repo_dir, false, tpl->path, tpl->mode, false, content);+ }+ }++ return error;+}++static int mkdir_parent(git_buf *buf, uint32_t mode, bool skip2)+{+ /* When making parent directories during repository initialization+ * don't try to set gid or grant world write access+ */+ return git_futils_mkdir(+ buf->ptr, NULL, mode & ~(S_ISGID | 0002),+ GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR |+ (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST));+}++static int repo_init_directories(+ git_buf *repo_path,+ git_buf *wd_path,+ const char *given_repo,+ git_repository_init_options *opts)+{+ int error = 0;+ bool is_bare, add_dotgit, has_dotgit, natural_wd;+ mode_t dirmode;++ /* There are three possible rules for what we are allowed to create:+ * - MKPATH means anything we need+ * - MKDIR means just the .git directory and its parent and the workdir+ * - Neither means only the .git directory can be created+ *+ * There are 5 "segments" of path that we might need to deal with:+ * 1. The .git directory+ * 2. The parent of the .git directory+ * 3. Everything above the parent of the .git directory+ * 4. The working directory (often the same as #2)+ * 5. Everything above the working directory (often the same as #3)+ *+ * For all directories created, we start with the init_mode value for+ * permissions and then strip off bits in some cases:+ *+ * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH+ * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID+ * For all rules, we create #1 using the untouched init_mode+ */++ /* set up repo path */++ is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);++ add_dotgit =+ (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 &&+ !is_bare &&+ git__suffixcmp(given_repo, "/" DOT_GIT) != 0 &&+ git__suffixcmp(given_repo, "/" GIT_DIR) != 0;++ if (git_buf_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0)+ return -1;++ has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0);+ if (has_dotgit)+ opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT;++ /* set up workdir path */++ if (!is_bare) {+ if (opts->workdir_path) {+ if (git_path_join_unrooted(+ wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0)+ return -1;+ } else if (has_dotgit) {+ if (git_path_dirname_r(wd_path, repo_path->ptr) < 0)+ return -1;+ } else {+ giterr_set(GITERR_REPOSITORY, "Cannot pick working directory"+ " for non-bare repository that isn't a '.git' directory");+ return -1;+ }++ if (git_path_to_dir(wd_path) < 0)+ return -1;+ } else {+ git_buf_clear(wd_path);+ }++ natural_wd =+ has_dotgit &&+ wd_path->size > 0 &&+ wd_path->size + strlen(GIT_DIR) == repo_path->size &&+ memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0;+ if (natural_wd)+ opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD;++ /* create directories as needed / requested */++ dirmode = pick_dir_mode(opts);++ if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) {+ /* create path #5 */+ if (wd_path->size > 0 &&+ (error = mkdir_parent(wd_path, dirmode, false)) < 0)+ return error;++ /* create path #3 (if not the same as #5) */+ if (!natural_wd &&+ (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0)+ return error;+ }++ if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||+ (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0)+ {+ /* create path #4 */+ if (wd_path->size > 0 &&+ (error = git_futils_mkdir(+ wd_path->ptr, NULL, dirmode & ~S_ISGID,+ GIT_MKDIR_VERIFY_DIR)) < 0)+ return error;++ /* create path #2 (if not the same as #4) */+ if (!natural_wd &&+ (error = git_futils_mkdir(+ repo_path->ptr, NULL, dirmode & ~S_ISGID,+ GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0)+ return error;+ }++ if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 ||+ (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 ||+ has_dotgit)+ {+ /* create path #1 */+ error = git_futils_mkdir(repo_path->ptr, NULL, dirmode,+ GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0));+ }++ /* prettify both directories now that they are created */++ if (!error) {+ error = git_path_prettify_dir(repo_path, repo_path->ptr, NULL);++ if (!error && wd_path->size > 0)+ error = git_path_prettify_dir(wd_path, wd_path->ptr, NULL);+ }++ return error;+}++static int repo_init_create_origin(git_repository *repo, const char *url)+{+ int error;+ git_remote *remote;++ if (!(error = git_remote_create(&remote, repo, GIT_REMOTE_ORIGIN, url))) {+ git_remote_free(remote);+ }++ return error;+}++int git_repository_init(+ git_repository **repo_out, const char *path, unsigned is_bare)+{+ git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;++ opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */+ if (is_bare)+ opts.flags |= GIT_REPOSITORY_INIT_BARE;++ return git_repository_init_ext(repo_out, path, &opts);+}++int git_repository_init_ext(+ git_repository **out,+ const char *given_repo,+ git_repository_init_options *opts)+{+ int error;+ git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT;++ assert(out && given_repo && opts);++ GITERR_CHECK_VERSION(opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION, "git_repository_init_options");++ error = repo_init_directories(&repo_path, &wd_path, given_repo, opts);+ if (error < 0)+ goto cleanup;++ if (valid_repository_path(&repo_path)) {++ if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) {+ giterr_set(GITERR_REPOSITORY,+ "Attempt to reinitialize '%s'", given_repo);+ error = GIT_EEXISTS;+ goto cleanup;+ }++ opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT;++ error = repo_init_config(+ git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts);++ /* TODO: reinitialize the templates */+ }+ else {+ if (!(error = repo_init_structure(+ git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)) &&+ !(error = repo_init_config(+ git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)))+ error = repo_init_create_head(+ git_buf_cstr(&repo_path), opts->initial_head);+ }+ if (error < 0)+ goto cleanup;++ error = git_repository_open(out, git_buf_cstr(&repo_path));++ if (!error && opts->origin_url)+ error = repo_init_create_origin(*out, opts->origin_url);++cleanup:+ git_buf_free(&repo_path);+ git_buf_free(&wd_path);++ return error;+}++int git_repository_head_detached(git_repository *repo)+{+ git_reference *ref;+ git_odb *odb = NULL;+ int exists;++ if (git_repository_odb__weakptr(&odb, repo) < 0)+ return -1;++ if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)+ return -1;++ if (git_reference_type(ref) == GIT_REF_SYMBOLIC) {+ git_reference_free(ref);+ return 0;+ }++ exists = git_odb_exists(odb, git_reference_target(ref));++ git_reference_free(ref);+ return exists;+}++int git_repository_head(git_reference **head_out, git_repository *repo)+{+ git_reference *head;+ int error;++ if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)+ return error;++ if (git_reference_type(head) == GIT_REF_OID) {+ *head_out = head;+ return 0;+ }++ error = git_reference_lookup_resolved(head_out, repo, git_reference_symbolic_target(head), -1);+ git_reference_free(head);++ return error == GIT_ENOTFOUND ? GIT_EORPHANEDHEAD : error;+}++int git_repository_head_orphan(git_repository *repo)+{+ git_reference *ref = NULL;+ int error;++ error = git_repository_head(&ref, repo);+ git_reference_free(ref);++ if (error == GIT_EORPHANEDHEAD)+ return 1;++ if (error < 0)+ return -1;++ return 0;+}++static int at_least_one_cb(const char *refname, void *payload)+{+ GIT_UNUSED(refname);+ GIT_UNUSED(payload);++ return GIT_EUSER;+}++static int repo_contains_no_reference(git_repository *repo)+{+ int error;+ + error = git_reference_foreach(repo, GIT_REF_LISTALL, at_least_one_cb, NULL);++ if (error == GIT_EUSER)+ return 0;++ return error == 0 ? 1 : error;+}++int git_repository_is_empty(git_repository *repo)+{+ git_reference *head = NULL;+ int error;++ if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0)+ return -1;++ if (!(error = git_reference_type(head) == GIT_REF_SYMBOLIC))+ goto cleanup;++ if (!(error = strcmp(+ git_reference_symbolic_target(head),+ GIT_REFS_HEADS_DIR "master") == 0))+ goto cleanup;++ error = repo_contains_no_reference(repo);++cleanup:+ git_reference_free(head);+ return error < 0 ? -1 : error;+}++const char *git_repository_path(git_repository *repo)+{+ assert(repo);+ return repo->path_repository;+}++const char *git_repository_workdir(git_repository *repo)+{+ assert(repo);++ if (repo->is_bare)+ return NULL;++ return repo->workdir;+}++int git_repository_set_workdir(+ git_repository *repo, const char *workdir, int update_gitlink)+{+ int error = 0;+ git_buf path = GIT_BUF_INIT;++ assert(repo && workdir);++ if (git_path_prettify_dir(&path, workdir, NULL) < 0)+ return -1;++ if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0)+ return 0;++ if (update_gitlink) {+ git_config *config;++ if (git_repository_config__weakptr(&config, repo) < 0)+ return -1;++ error = repo_write_gitlink(path.ptr, git_repository_path(repo));++ /* passthrough error means gitlink is unnecessary */+ if (error == GIT_PASSTHROUGH)+ error = git_config_delete_entry(config, "core.worktree");+ else if (!error)+ error = git_config_set_string(config, "core.worktree", path.ptr);++ if (!error)+ error = git_config_set_bool(config, "core.bare", false);+ }++ if (!error) {+ char *old_workdir = repo->workdir;++ repo->workdir = git_buf_detach(&path);+ repo->is_bare = 0;++ git__free(old_workdir);+ }++ return error;+}++int git_repository_is_bare(git_repository *repo)+{+ assert(repo);+ return repo->is_bare;+}++int git_repository_head_tree(git_tree **tree, git_repository *repo)+{+ git_reference *head;+ git_object *obj;+ int error;++ if ((error = git_repository_head(&head, repo)) < 0)+ return error;++ if ((error = git_reference_peel(&obj, head, GIT_OBJ_TREE)) < 0)+ goto cleanup;++ *tree = (git_tree *)obj;++cleanup:+ git_reference_free(head);+ return error;+}++int git_repository_message(char *buffer, size_t len, git_repository *repo)+{+ git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;+ struct stat st;+ int error;++ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)+ return -1;++ if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {+ if (errno == ENOENT)+ error = GIT_ENOTFOUND;+ }+ else if (buffer != NULL) {+ error = git_futils_readbuffer(&buf, git_buf_cstr(&path));+ git_buf_copy_cstr(buffer, len, &buf);+ }++ git_buf_free(&path);+ git_buf_free(&buf);++ if (!error)+ error = (int)st.st_size + 1; /* add 1 for NUL byte */++ return error;+}++int git_repository_message_remove(git_repository *repo)+{+ git_buf path = GIT_BUF_INIT;+ int error;++ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)+ return -1;++ error = p_unlink(git_buf_cstr(&path));+ git_buf_free(&path);++ return error;+}++int git_repository_hashfile(+ git_oid *out,+ git_repository *repo,+ const char *path,+ git_otype type,+ const char *as_path)+{+ int error;+ git_vector filters = GIT_VECTOR_INIT;+ git_file fd = -1;+ git_off_t len;+ git_buf full_path = GIT_BUF_INIT;++ assert(out && path && repo); /* as_path can be NULL */++ /* At some point, it would be nice if repo could be NULL to just+ * apply filter rules defined in system and global files, but for+ * now that is not possible because git_filters_load() needs it.+ */++ error = git_path_join_unrooted(+ &full_path, path, repo ? git_repository_workdir(repo) : NULL, NULL);+ if (error < 0)+ return error;++ if (!as_path)+ as_path = path;++ /* passing empty string for "as_path" indicated --no-filters */+ if (strlen(as_path) > 0) {+ error = git_filters_load(&filters, repo, as_path, GIT_FILTER_TO_ODB);+ if (error < 0)+ return error;+ } else {+ error = 0;+ }++ /* at this point, error is a count of the number of loaded filters */++ fd = git_futils_open_ro(full_path.ptr);+ if (fd < 0) {+ error = fd;+ goto cleanup;+ }++ len = git_futils_filesize(fd);+ if (len < 0) {+ error = (int)len;+ goto cleanup;+ }++ if (!git__is_sizet(len)) {+ giterr_set(GITERR_OS, "File size overflow for 32-bit systems");+ error = -1;+ goto cleanup;+ }++ error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, &filters);++cleanup:+ if (fd >= 0)+ p_close(fd);+ git_filters_free(&filters);+ git_buf_free(&full_path);++ return error;+}++static bool looks_like_a_branch(const char *refname)+{+ return git__prefixcmp(refname, GIT_REFS_HEADS_DIR) == 0;+}++int git_repository_set_head(+ git_repository* repo,+ const char* refname)+{+ git_reference *ref,+ *new_head = NULL;+ int error;++ assert(repo && refname);++ error = git_reference_lookup(&ref, repo, refname);+ if (error < 0 && error != GIT_ENOTFOUND)+ return error;++ if (!error) {+ if (git_reference_is_branch(ref))+ error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, git_reference_name(ref), 1);+ else+ error = git_repository_set_head_detached(repo, git_reference_target(ref));+ } else if (looks_like_a_branch(refname))+ error = git_reference_symbolic_create(&new_head, repo, GIT_HEAD_FILE, refname, 1);++ git_reference_free(ref);+ git_reference_free(new_head);+ return error;+}++int git_repository_set_head_detached(+ git_repository* repo,+ const git_oid* commitish)+{+ int error;+ git_object *object,+ *peeled = NULL;+ git_reference *new_head = NULL;++ assert(repo && commitish);++ if ((error = git_object_lookup(&object, repo, commitish, GIT_OBJ_ANY)) < 0)+ return error;++ if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0)+ goto cleanup;++ error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), 1);++cleanup:+ git_object_free(object);+ git_object_free(peeled);+ git_reference_free(new_head);+ return error;+}++int git_repository_detach_head(+ git_repository* repo)+{+ git_reference *old_head = NULL,+ *new_head = NULL;+ git_object *object = NULL;+ int error;++ assert(repo);++ if ((error = git_repository_head(&old_head, repo)) < 0)+ return error;++ if ((error = git_object_lookup(&object, repo, git_reference_target(old_head), GIT_OBJ_COMMIT)) < 0)+ goto cleanup;++ error = git_reference_create(&new_head, repo, GIT_HEAD_FILE, git_reference_target(old_head), 1);++cleanup:+ git_object_free(object);+ git_reference_free(old_head);+ git_reference_free(new_head);+ return error;+}++/**+ * Loosely ported from git.git+ * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289+ */+int git_repository_state(git_repository *repo)+{+ git_buf repo_path = GIT_BUF_INIT;+ int state = GIT_REPOSITORY_STATE_NONE;++ assert(repo);++ if (git_buf_puts(&repo_path, repo->path_repository) < 0)+ return -1;++ if (git_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE))+ state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE;+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR))+ state = GIT_REPOSITORY_STATE_REBASE_MERGE;+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE))+ state = GIT_REPOSITORY_STATE_REBASE;+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE))+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX;+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR))+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE;+ else if (git_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE))+ state = GIT_REPOSITORY_STATE_MERGE;+ else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE))+ state = GIT_REPOSITORY_STATE_REVERT;+ else if(git_path_contains_file(&repo_path, GIT_CHERRY_PICK_HEAD_FILE))+ state = GIT_REPOSITORY_STATE_CHERRY_PICK;+ else if(git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE))+ state = GIT_REPOSITORY_STATE_BISECT;++ git_buf_free(&repo_path);+ return state; }
libgit2/src/repository.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -18,8 +18,10 @@ #include "refs.h" #include "buffer.h" #include "odb.h"-#include "attr.h"+#include "object.h"+#include "attrcache.h" #include "strmap.h"+#include "refdb.h" #define DOT_GIT ".git" #define GIT_DIR DOT_GIT "/"@@ -68,20 +70,21 @@ GIT_EOL_DEFAULT = GIT_EOL_NATIVE } git_cvar_value; -/** Base git object for inheritance */-struct git_object {- git_cached_obj cached;- git_repository *repo;- git_otype type;+/* internal repository init flags */+enum {+ GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16),+ GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17),+ GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18), }; +/** Internal structure for repository object */ struct git_repository { git_odb *_odb;+ git_refdb *_refdb; git_config *_config; git_index *_index; git_cache objects;- git_refcache references; git_attr_cache attrcache; git_strmap *submodules; @@ -94,15 +97,6 @@ git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; }; -/* fully free the object; internal method, do not- * export */-void git_object__free(void *object);--int git_object__resolve_to_type(git_object **obj, git_otype type);--int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header);-void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid);- GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) { return &repo->attrcache;@@ -119,10 +113,11 @@ */ int git_repository_config__weakptr(git_config **out, git_repository *repo); int git_repository_odb__weakptr(git_odb **out, git_repository *repo);+int git_repository_refdb__weakptr(git_refdb **out, git_repository *repo); int git_repository_index__weakptr(git_index **out, git_repository *repo); /*- * CVAR cache + * CVAR cache * * Efficient access to the most used config variables of a repository. * The cache is cleared everytime the config backend is replaced.@@ -134,5 +129,20 @@ * Submodule cache */ extern void git_submodule_config_free(git_repository *repo);++GIT_INLINE(int) git_repository__ensure_not_bare(+ git_repository *repo,+ const char *operation_name)+{+ if (!git_repository_is_bare(repo))+ return 0;++ giterr_set(+ GITERR_REPOSITORY,+ "Cannot %s. This operation is not allowed against bare repositories.",+ operation_name);++ return GIT_EBAREREPO;+} #endif
+ libgit2/src/reset.c view
@@ -0,0 +1,163 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "common.h"+#include "commit.h"+#include "tag.h"+#include "merge.h"+#include "diff.h"+#include "git2/reset.h"+#include "git2/checkout.h"+#include "git2/merge.h"+#include "git2/refs.h"++#define ERROR_MSG "Cannot perform reset"++int git_reset_default(+ git_repository *repo,+ git_object *target,+ git_strarray* pathspecs)+{+ git_object *commit = NULL;+ git_tree *tree = NULL;+ git_diff_list *diff = NULL;+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;+ size_t i;+ git_diff_delta *delta;+ git_index_entry entry;+ int error;+ git_index *index = NULL;++ assert(pathspecs != NULL && pathspecs->count > 0); ++ memset(&entry, 0, sizeof(git_index_entry));++ if ((error = git_repository_index(&index, repo)) < 0)+ goto cleanup;++ if (target) {+ if (git_object_owner(target) != repo) {+ giterr_set(GITERR_OBJECT,+ "%s_default - The given target does not belong to this repository.", ERROR_MSG);+ return -1;+ }++ if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||+ (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)+ goto cleanup;+ }++ opts.pathspec = *pathspecs;+ opts.flags = GIT_DIFF_REVERSE;++ if ((error = git_diff_tree_to_index(+ &diff, repo, tree, index, &opts)) < 0)+ goto cleanup;++ git_vector_foreach(&diff->deltas, i, delta) {+ if ((error = git_index_conflict_remove(index, delta->old_file.path)) < 0)+ goto cleanup;++ assert(delta->status == GIT_DELTA_ADDED ||+ delta->status == GIT_DELTA_MODIFIED ||+ delta->status == GIT_DELTA_DELETED);++ if (delta->status == GIT_DELTA_DELETED) {+ if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0)+ goto cleanup;+ } else {+ entry.mode = delta->new_file.mode;+ git_oid_cpy(&entry.oid, &delta->new_file.oid);+ entry.path = (char *)delta->new_file.path;++ if ((error = git_index_add(index, &entry)) < 0)+ goto cleanup;+ }+ }++ error = git_index_write(index);++cleanup:+ git_object_free(commit);+ git_tree_free(tree);+ git_index_free(index);+ git_diff_list_free(diff);++ return error;+}++int git_reset(+ git_repository *repo,+ git_object *target,+ git_reset_t reset_type)+{+ git_object *commit = NULL;+ git_index *index = NULL;+ git_tree *tree = NULL;+ int error = 0;+ git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;++ assert(repo && target);++ if (git_object_owner(target) != repo) {+ giterr_set(GITERR_OBJECT,+ "%s - The given target does not belong to this repository.", ERROR_MSG);+ return -1;+ }++ if (reset_type != GIT_RESET_SOFT &&+ (error = git_repository__ensure_not_bare(repo,+ reset_type == GIT_RESET_MIXED ? "reset mixed" : "reset hard")) < 0)+ return error;++ if ((error = git_object_peel(&commit, target, GIT_OBJ_COMMIT)) < 0 ||+ (error = git_repository_index(&index, repo)) < 0 ||+ (error = git_commit_tree(&tree, (git_commit *)commit)) < 0)+ goto cleanup;++ if (reset_type == GIT_RESET_SOFT &&+ (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE ||+ git_index_has_conflicts(index)))+ {+ giterr_set(GITERR_OBJECT, "%s (soft) in the middle of a merge.", ERROR_MSG);+ error = GIT_EUNMERGED;+ goto cleanup;+ }++ /* move HEAD to the new target */+ if ((error = git_reference__update_terminal(repo, GIT_HEAD_FILE,+ git_object_id(commit))) < 0)+ goto cleanup;++ if (reset_type == GIT_RESET_HARD) {+ /* overwrite working directory with HEAD */+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;++ if ((error = git_checkout_tree(repo, (git_object *)tree, &opts)) < 0)+ goto cleanup;+ }++ if (reset_type > GIT_RESET_SOFT) {+ /* reset index to the target content */++ if ((error = git_index_read_tree(index, tree)) < 0 ||+ (error = git_index_write(index)) < 0)+ goto cleanup;++ if ((error = git_repository_merge_cleanup(repo)) < 0) {+ giterr_set(GITERR_INDEX, "%s - failed to clean up merge data", ERROR_MSG);+ goto cleanup;+ }+ }++cleanup:+ git_object_free(commit);+ git_index_free(index);+ git_tree_free(tree);++ return error;+}
+ libgit2/src/revparse.c view
@@ -0,0 +1,912 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include <assert.h>++#include "common.h"+#include "buffer.h"+#include "tree.h"+#include "refdb.h"++#include "git2.h"++static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname)+{+ int error, i;+ bool fallbackmode = true;+ git_reference *ref;+ git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT;++ static const char* formatters[] = {+ "%s",+ GIT_REFS_DIR "%s",+ GIT_REFS_TAGS_DIR "%s",+ GIT_REFS_HEADS_DIR "%s",+ GIT_REFS_REMOTES_DIR "%s",+ GIT_REFS_REMOTES_DIR "%s/" GIT_HEAD_FILE,+ NULL+ };++ if (*refname)+ git_buf_puts(&name, refname);+ else {+ git_buf_puts(&name, GIT_HEAD_FILE);+ fallbackmode = false;+ }++ for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) {++ git_buf_clear(&refnamebuf);++ if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0)+ goto cleanup;++ if (!git_reference_is_valid_name(git_buf_cstr(&refnamebuf))) {+ error = GIT_EINVALIDSPEC;+ continue;+ }++ error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1);++ if (!error) {+ *out = ref;+ error = 0;+ goto cleanup;+ }++ if (error != GIT_ENOTFOUND)+ goto cleanup;+ }++cleanup:+ git_buf_free(&name);+ git_buf_free(&refnamebuf);+ return error;+}++static int maybe_sha_or_abbrev(git_object** out, git_repository *repo, const char *spec, size_t speclen)+{+ git_oid oid;++ if (git_oid_fromstrn(&oid, spec, speclen) < 0)+ return GIT_ENOTFOUND;++ return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY);+}++static int maybe_sha(git_object** out, git_repository *repo, const char *spec)+{+ size_t speclen = strlen(spec);++ if (speclen != GIT_OID_HEXSZ)+ return GIT_ENOTFOUND;++ return maybe_sha_or_abbrev(out, repo, spec, speclen);+}++static int maybe_abbrev(git_object** out, git_repository *repo, const char *spec)+{+ size_t speclen = strlen(spec);++ return maybe_sha_or_abbrev(out, repo, spec, speclen);+}++static int build_regex(regex_t *regex, const char *pattern)+{+ int error;++ if (*pattern == '\0') {+ giterr_set(GITERR_REGEX, "Empty pattern");+ return GIT_EINVALIDSPEC;+ }++ error = regcomp(regex, pattern, REG_EXTENDED);+ if (!error)+ return 0;++ error = giterr_set_regex(regex, error);++ regfree(regex);++ return error;+}++static int maybe_describe(git_object**out, git_repository *repo, const char *spec)+{+ const char *substr;+ int error;+ regex_t regex;++ substr = strstr(spec, "-g");++ if (substr == NULL)+ return GIT_ENOTFOUND;++ if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)+ return -1;++ error = regexec(®ex, spec, 0, NULL, 0);+ regfree(®ex);++ if (error)+ return GIT_ENOTFOUND;++ return maybe_abbrev(out, repo, substr+2);+}++static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec)+{+ int error;+ git_reference *ref;++ error = maybe_sha(out, repo, spec);+ if (!error)+ return 0;++ if (error < 0 && error != GIT_ENOTFOUND)+ return error;++ error = disambiguate_refname(&ref, repo, spec);+ if (!error) {+ error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);+ git_reference_free(ref);+ return error;+ }++ if (error < 0 && error != GIT_ENOTFOUND)+ return error;++ error = maybe_abbrev(out, repo, spec);+ if (!error)+ return 0;++ if (error < 0 && error != GIT_ENOTFOUND)+ return error;++ error = maybe_describe(out, repo, spec);+ if (!error)+ return 0;++ if (error < 0 && error != GIT_ENOTFOUND)+ return error;++ giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec);+ return GIT_ENOTFOUND;+}++static int try_parse_numeric(int *n, const char *curly_braces_content)+{+ int32_t content;+ const char *end_ptr;++ if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0)+ return -1;++ if (*end_ptr != '\0')+ return -1;++ *n = (int)content;+ return 0;+}++static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)+{+ git_reference *ref = NULL;+ git_reflog *reflog = NULL;+ regex_t preg;+ int error = -1;+ size_t i, numentries, cur;+ const git_reflog_entry *entry;+ const char *msg;+ regmatch_t regexmatches[2];+ git_buf buf = GIT_BUF_INIT;++ cur = position;++ if (*identifier != '\0' || *base_ref != NULL)+ return GIT_EINVALIDSPEC;++ if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0)+ return -1;++ if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0)+ goto cleanup;++ if (git_reflog_read(&reflog, ref) < 0)+ goto cleanup;++ numentries = git_reflog_entrycount(reflog);++ for (i = 0; i < numentries; i++) {+ entry = git_reflog_entry_byindex(reflog, i);+ msg = git_reflog_entry_message(entry);++ if (regexec(&preg, msg, 2, regexmatches, 0))+ continue;++ cur--;++ if (cur > 0)+ continue;++ git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so);++ if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0)+ goto cleanup;++ if (error < 0 && error != GIT_ENOTFOUND)+ goto cleanup;++ error = maybe_abbrev(out, repo, git_buf_cstr(&buf));++ goto cleanup;+ }++ error = GIT_ENOTFOUND;++cleanup:+ git_reference_free(ref);+ git_buf_free(&buf);+ regfree(&preg);+ git_reflog_free(reflog);+ return error;+}++static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, size_t identifier)+{+ git_reflog *reflog;+ int error = -1;+ size_t numentries;+ const git_reflog_entry *entry;+ bool search_by_pos = (identifier <= 100000000);++ if (git_reflog_read(&reflog, ref) < 0)+ return -1;++ numentries = git_reflog_entrycount(reflog);++ if (search_by_pos) {+ if (numentries < identifier + 1) {+ giterr_set(+ GITERR_REFERENCE,+ "Reflog for '%s' has only "PRIuZ" entries, asked for "PRIuZ,+ git_reference_name(ref), numentries, identifier);++ error = GIT_ENOTFOUND;+ goto cleanup;+ }++ entry = git_reflog_entry_byindex(reflog, identifier);+ git_oid_cpy(oid, git_reflog_entry_id_new(entry));+ error = 0;+ goto cleanup;++ } else {+ size_t i;+ git_time commit_time;++ for (i = 0; i < numentries; i++) {+ entry = git_reflog_entry_byindex(reflog, i);+ commit_time = git_reflog_entry_committer(entry)->when;++ if (commit_time.time > (git_time_t)identifier)+ continue;++ git_oid_cpy(oid, git_reflog_entry_id_new(entry));+ error = 0;+ goto cleanup;+ }++ error = GIT_ENOTFOUND;+ }++cleanup:+ git_reflog_free(reflog);+ return error;+}++static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, size_t position)+{+ git_reference *ref;+ git_oid oid;+ int error = -1;++ if (*base_ref == NULL) {+ if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)+ return error;+ } else {+ ref = *base_ref;+ *base_ref = NULL;+ }++ if (position == 0) {+ error = git_object_lookup(out, repo, git_reference_target(ref), GIT_OBJ_ANY);+ goto cleanup;+ }++ if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0)+ goto cleanup;++ error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY);++cleanup:+ git_reference_free(ref);+ return error;+}++static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo)+{+ git_reference *tracking, *ref;+ int error = -1;++ if (*base_ref == NULL) {+ if ((error = disambiguate_refname(&ref, repo, identifier)) < 0)+ return error;+ } else {+ ref = *base_ref;+ *base_ref = NULL;+ }++ if (!git_reference_is_branch(ref)) {+ error = GIT_EINVALIDSPEC;+ goto cleanup;+ }++ if ((error = git_branch_upstream(&tracking, ref)) < 0)+ goto cleanup;++ *base_ref = tracking;++cleanup:+ git_reference_free(ref);+ return error;+}++static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository* repo, const char *curly_braces_content)+{+ bool is_numeric;+ int parsed = 0, error = -1;+ git_buf identifier = GIT_BUF_INIT;+ git_time_t timestamp;++ assert(*out == NULL);++ if (git_buf_put(&identifier, spec, identifier_len) < 0)+ return -1;++ is_numeric = !try_parse_numeric(&parsed, curly_braces_content);++ if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) {+ error = GIT_EINVALIDSPEC;+ goto cleanup;+ }++ if (is_numeric) {+ if (parsed < 0)+ error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, git_buf_cstr(&identifier), -parsed);+ else+ error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed);++ goto cleanup;+ }++ if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) {+ error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo);++ goto cleanup;+ }++ if (git__date_parse(×tamp, curly_braces_content) < 0)+ goto cleanup;++ error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (size_t)timestamp);++cleanup:+ git_buf_free(&identifier);+ return error;+}++static git_otype parse_obj_type(const char *str)+{+ if (!strcmp(str, "commit"))+ return GIT_OBJ_COMMIT;++ if (!strcmp(str, "tree"))+ return GIT_OBJ_TREE;++ if (!strcmp(str, "blob"))+ return GIT_OBJ_BLOB;++ if (!strcmp(str, "tag"))+ return GIT_OBJ_TAG;++ return GIT_OBJ_BAD;+}++static int dereference_to_non_tag(git_object **out, git_object *obj)+{+ if (git_object_type(obj) == GIT_OBJ_TAG)+ return git_tag_peel(out, (git_tag *)obj);++ return git_object_dup(out, obj);+}++static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n)+{+ git_object *temp_commit = NULL;+ int error;++ if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)+ return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?+ GIT_EINVALIDSPEC : error;++ if (n == 0) {+ *out = temp_commit;+ return 0;+ }++ error = git_commit_parent((git_commit **)out, (git_commit*)temp_commit, n - 1);++ git_object_free(temp_commit);+ return error;+}++static int handle_linear_syntax(git_object **out, git_object *obj, int n)+{+ git_object *temp_commit = NULL;+ int error;++ if ((error = git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT)) < 0)+ return (error == GIT_EAMBIGUOUS || error == GIT_ENOTFOUND) ?+ GIT_EINVALIDSPEC : error;++ error = git_commit_nth_gen_ancestor((git_commit **)out, (git_commit*)temp_commit, n);++ git_object_free(temp_commit);+ return error;+}++static int handle_colon_syntax(+ git_object **out,+ git_object *obj,+ const char *path)+{+ git_object *tree;+ int error = -1;+ git_tree_entry *entry = NULL;++ if ((error = git_object_peel(&tree, obj, GIT_OBJ_TREE)) < 0)+ return error == GIT_ENOTFOUND ? GIT_EINVALIDSPEC : error;++ if (*path == '\0') {+ *out = tree;+ return 0;+ }++ /*+ * TODO: Handle the relative path syntax+ * (:./relative/path and :../relative/path)+ */+ if ((error = git_tree_entry_bypath(&entry, (git_tree *)tree, path)) < 0)+ goto cleanup;++ error = git_tree_entry_to_object(out, git_object_owner(tree), entry);++cleanup:+ git_tree_entry_free(entry);+ git_object_free(tree);++ return error;+}++static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex)+{+ int error;+ git_oid oid;+ git_object *obj;++ while (!(error = git_revwalk_next(&oid, walk))) {++ error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT);+ if ((error < 0) && (error != GIT_ENOTFOUND))+ return -1;++ if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) {+ *out = obj;+ return 0;+ }++ git_object_free(obj);+ }++ if (error < 0 && error == GIT_ITEROVER)+ error = GIT_ENOTFOUND;++ return error;+}++static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern)+{+ regex_t preg;+ git_revwalk *walk = NULL;+ int error;++ if ((error = build_regex(&preg, pattern)) < 0)+ return error;++ if ((error = git_revwalk_new(&walk, repo)) < 0)+ goto cleanup;++ git_revwalk_sorting(walk, GIT_SORT_TIME);++ if (spec_oid == NULL) {+ // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails+ if ((error = git_revwalk_push_glob(walk, GIT_REFS_HEADS_DIR "*")) < 0)+ goto cleanup;+ } else if ((error = git_revwalk_push(walk, spec_oid)) < 0)+ goto cleanup;++ error = walk_and_search(out, walk, &preg);++cleanup:+ regfree(&preg);+ git_revwalk_free(walk);++ return error;+}++static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content)+{+ git_otype expected_type;++ if (*curly_braces_content == '\0')+ return dereference_to_non_tag(out, obj);++ if (*curly_braces_content == '/')+ return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1);++ expected_type = parse_obj_type(curly_braces_content);++ if (expected_type == GIT_OBJ_BAD)+ return GIT_EINVALIDSPEC;++ return git_object_peel(out, obj, expected_type);+}++static int extract_curly_braces_content(git_buf *buf, const char *spec, size_t *pos)+{+ git_buf_clear(buf);++ assert(spec[*pos] == '^' || spec[*pos] == '@');++ (*pos)++;++ if (spec[*pos] == '\0' || spec[*pos] != '{')+ return GIT_EINVALIDSPEC;++ (*pos)++;++ while (spec[*pos] != '}') {+ if (spec[*pos] == '\0')+ return GIT_EINVALIDSPEC;++ git_buf_putc(buf, spec[(*pos)++]);+ }++ (*pos)++;++ return 0;+}++static int extract_path(git_buf *buf, const char *spec, size_t *pos)+{+ git_buf_clear(buf);++ assert(spec[*pos] == ':');++ (*pos)++;++ if (git_buf_puts(buf, spec + *pos) < 0)+ return -1;++ *pos += git_buf_len(buf);++ return 0;+}++static int extract_how_many(int *n, const char *spec, size_t *pos)+{+ const char *end_ptr;+ int parsed, accumulated;+ char kind = spec[*pos];++ assert(spec[*pos] == '^' || spec[*pos] == '~');++ accumulated = 0;++ do {+ do {+ (*pos)++;+ accumulated++;+ } while (spec[(*pos)] == kind && kind == '~');++ if (git__isdigit(spec[*pos])) {+ if (git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0)+ return GIT_EINVALIDSPEC;++ accumulated += (parsed - 1);+ *pos = end_ptr - spec;+ }++ } while (spec[(*pos)] == kind && kind == '~');++ *n = accumulated;++ return 0;+}++static int object_from_reference(git_object **object, git_reference *reference)+{+ git_reference *resolved = NULL;+ int error;++ if (git_reference_resolve(&resolved, reference) < 0)+ return -1;++ error = git_object_lookup(object, reference->db->repo, git_reference_target(resolved), GIT_OBJ_ANY);+ git_reference_free(resolved);++ return error;+}++static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier)+{+ int error;+ git_buf identifier = GIT_BUF_INIT;++ if (*object != NULL)+ return 0;++ if (*reference != NULL) {+ if ((error = object_from_reference(object, *reference)) < 0)+ return error;++ git_reference_free(*reference);+ *reference = NULL;+ return 0;+ }++ if (!allow_empty_identifier && identifier_len == 0)+ return GIT_EINVALIDSPEC;++ if (git_buf_put(&identifier, spec, identifier_len) < 0)+ return -1;++ error = revparse_lookup_object(object, repo, git_buf_cstr(&identifier));+ git_buf_free(&identifier);++ return error;+}++static int ensure_base_rev_is_not_known_yet(git_object *object)+{+ if (object == NULL)+ return 0;++ return GIT_EINVALIDSPEC;+}++static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len)+{+ if (object != NULL)+ return true;++ if (reference != NULL)+ return true;++ if (identifier_len > 0)+ return true;++ return false;+}++static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference)+{+ if (!ensure_base_rev_is_not_known_yet(object) && reference == NULL)+ return 0;++ return GIT_EINVALIDSPEC;+}++int git_revparse_single(git_object **out, git_repository *repo, const char *spec)+{+ size_t pos = 0, identifier_len = 0;+ int error = -1, n;+ git_buf buf = GIT_BUF_INIT;++ git_reference *reference = NULL;+ git_object *base_rev = NULL;++ assert(out && repo && spec);++ *out = NULL;++ while (spec[pos]) {+ switch (spec[pos]) {+ case '^':+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)+ goto cleanup;++ if (spec[pos+1] == '{') {+ git_object *temp_object = NULL;++ if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)+ goto cleanup;++ if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)+ goto cleanup;++ git_object_free(base_rev);+ base_rev = temp_object;+ } else {+ git_object *temp_object = NULL;++ if ((error = extract_how_many(&n, spec, &pos)) < 0)+ goto cleanup;++ if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0)+ goto cleanup;++ git_object_free(base_rev);+ base_rev = temp_object;+ }+ break;++ case '~':+ {+ git_object *temp_object = NULL;++ if ((error = extract_how_many(&n, spec, &pos)) < 0)+ goto cleanup;++ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)+ goto cleanup;++ if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0)+ goto cleanup;++ git_object_free(base_rev);+ base_rev = temp_object;+ break;+ }++ case ':':+ {+ git_object *temp_object = NULL;++ if ((error = extract_path(&buf, spec, &pos)) < 0)+ goto cleanup;++ if (any_left_hand_identifier(base_rev, reference, identifier_len)) {+ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0)+ goto cleanup;++ if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0)+ goto cleanup;+ } else {+ if (*git_buf_cstr(&buf) == '/') {+ if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0)+ goto cleanup;+ } else {++ /*+ * TODO: support merge-stage path lookup (":2:Makefile")+ * and plain index blob lookup (:i-am/a/blob)+ */+ giterr_set(GITERR_INVALID, "Unimplemented");+ error = GIT_ERROR;+ goto cleanup;+ }+ }++ git_object_free(base_rev);+ base_rev = temp_object;+ break;+ }++ case '@':+ {+ if (spec[pos+1] == '{') {+ git_object *temp_object = NULL;++ if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0)+ goto cleanup;++ if ((error = ensure_base_rev_is_not_known_yet(base_rev)) < 0)+ goto cleanup;++ if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0)+ goto cleanup;++ if (temp_object != NULL)+ base_rev = temp_object;+ break;+ } else {+ /* Fall through */+ }+ }++ default:+ if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference)) < 0)+ goto cleanup;++ pos++;+ identifier_len++;+ }+ }++ if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0)+ goto cleanup;++ *out = base_rev;+ error = 0;++cleanup:+ if (error) {+ if (error == GIT_EINVALIDSPEC)+ giterr_set(GITERR_INVALID,+ "Failed to parse revision specifier - Invalid pattern '%s'", spec);++ git_object_free(base_rev);+ }+ git_reference_free(reference);+ git_buf_free(&buf);+ return error;+}+++int git_revparse(+ git_revspec *revspec,+ git_repository *repo,+ const char *spec)+{+ const char *dotdot;+ int error = 0;++ assert(revspec && repo && spec);++ memset(revspec, 0x0, sizeof(*revspec));++ if ((dotdot = strstr(spec, "..")) != NULL) {+ char *lstr;+ const char *rstr;+ revspec->flags = GIT_REVPARSE_RANGE;++ lstr = git__substrdup(spec, dotdot - spec);+ rstr = dotdot + 2;+ if (dotdot[2] == '.') {+ revspec->flags |= GIT_REVPARSE_MERGE_BASE;+ rstr++;+ }++ if ((error = git_revparse_single(&revspec->from, repo, lstr)) < 0) {+ return error;+ }++ if ((error = git_revparse_single(&revspec->to, repo, rstr)) < 0) {+ return error;+ }++ git__free((void*)lstr);+ } else {+ revspec->flags = GIT_REVPARSE_SINGLE;+ error = git_revparse_single(&revspec->from, repo, spec);+ }++ return error;+}+
libgit2/src/revwalk.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,146 +8,18 @@ #include "common.h" #include "commit.h" #include "odb.h"-#include "pqueue.h" #include "pool.h"-#include "oidmap.h" -#include "git2/revwalk.h"-#include "git2/merge.h"+#include "revwalk.h"+#include "git2/revparse.h"+#include "merge.h" #include <regex.h> -GIT__USE_OIDMAP;--#define PARENT1 (1 << 0)-#define PARENT2 (1 << 1)-#define RESULT (1 << 2)-#define STALE (1 << 3)--typedef struct commit_object {- git_oid oid;- uint32_t time;- unsigned int seen:1,- uninteresting:1,- topo_delay:1,- parsed:1,- flags : 4;-- unsigned short in_degree;- unsigned short out_degree;-- struct commit_object **parents;-} commit_object;--typedef struct commit_list {- commit_object *item;- struct commit_list *next;-} commit_list;--struct git_revwalk {- git_repository *repo;- git_odb *odb;-- git_oidmap *commits;- git_pool commit_pool;-- commit_list *iterator_topo;- commit_list *iterator_rand;- commit_list *iterator_reverse;- git_pqueue iterator_time;-- int (*get_next)(commit_object **, git_revwalk *);- int (*enqueue)(git_revwalk *, commit_object *);-- unsigned walking:1;- unsigned int sorting;-- /* merge base calculation */- commit_object *one;- git_vector twos;-};--static int commit_time_cmp(void *a, void *b)-{- commit_object *commit_a = (commit_object *)a;- commit_object *commit_b = (commit_object *)b;-- return (commit_a->time < commit_b->time);-}--static commit_list *commit_list_insert(commit_object *item, commit_list **list_p)-{- commit_list *new_list = git__malloc(sizeof(commit_list));- if (new_list != NULL) {- new_list->item = item;- new_list->next = *list_p;- }- *list_p = new_list;- return new_list;-}--static commit_list *commit_list_insert_by_date(commit_object *item, commit_list **list_p)-{- commit_list **pp = list_p;- commit_list *p;-- while ((p = *pp) != NULL) {- if (commit_time_cmp(p->item, item) < 0)- break;-- pp = &p->next;- }-- return commit_list_insert(item, pp);-}-static void commit_list_free(commit_list **list_p)-{- commit_list *list = *list_p;-- while (list) {- commit_list *temp = list;- list = temp->next;- git__free(temp);- }-- *list_p = NULL;-}--static commit_object *commit_list_pop(commit_list **stack)-{- commit_list *top = *stack;- commit_object *item = top ? top->item : NULL;-- if (top) {- *stack = top->next;- git__free(top);- }- return item;-}--#define PARENTS_PER_COMMIT 2-#define COMMIT_ALLOC \- (sizeof(commit_object) + PARENTS_PER_COMMIT * sizeof(commit_object *))--static commit_object *alloc_commit(git_revwalk *walk)-{- return (commit_object *)git_pool_malloc(&walk->commit_pool, COMMIT_ALLOC);-}--static commit_object **alloc_parents(- git_revwalk *walk, commit_object *commit, size_t n_parents)-{- if (n_parents <= PARENTS_PER_COMMIT)- return (commit_object **)((char *)commit + sizeof(commit_object));-- return (commit_object **)git_pool_malloc(- &walk->commit_pool, (uint32_t)(n_parents * sizeof(commit_object *)));-}---static commit_object *commit_lookup(git_revwalk *walk, const git_oid *oid)+git_commit_list_node *git_revwalk__commit_lookup(+ git_revwalk *walk, const git_oid *oid) {- commit_object *commit;+ git_commit_list_node *commit; khiter_t pos; int ret; @@ -156,7 +28,7 @@ if (pos != kh_end(walk->commits)) return kh_value(walk->commits, pos); - commit = alloc_commit(walk);+ commit = git_commit_list_alloc_node(walk); if (commit == NULL) return NULL; @@ -169,227 +41,7 @@ return commit; } -static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawobj *raw)-{- const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1;-- unsigned char *buffer = raw->data;- unsigned char *buffer_end = buffer + raw->len;- unsigned char *parents_start;-- int i, parents = 0;- int commit_time;-- buffer += strlen("tree ") + GIT_OID_HEXSZ + 1;-- parents_start = buffer;- while (buffer + parent_len < buffer_end && memcmp(buffer, "parent ", strlen("parent ")) == 0) {- parents++;- buffer += parent_len;- }-- commit->parents = alloc_parents(walk, commit, parents);- GITERR_CHECK_ALLOC(commit->parents);-- buffer = parents_start;- for (i = 0; i < parents; ++i) {- git_oid oid;-- if (git_oid_fromstr(&oid, (char *)buffer + strlen("parent ")) < 0)- return -1;-- commit->parents[i] = commit_lookup(walk, &oid);- if (commit->parents[i] == NULL)- return -1;-- buffer += parent_len;- }-- commit->out_degree = (unsigned short)parents;-- if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) {- giterr_set(GITERR_ODB, "Failed to parse commit. Object is corrupted");- return -1;- }-- buffer = memchr(buffer, '>', buffer_end - buffer);- if (buffer == NULL) {- giterr_set(GITERR_ODB, "Failed to parse commit. Can't find author");- return -1;- }-- if (git__strtol32(&commit_time, (char *)buffer + 2, NULL, 10) < 0) {- giterr_set(GITERR_ODB, "Failed to parse commit. Can't parse commit time");- return -1;- }-- commit->time = (time_t)commit_time;- commit->parsed = 1;- return 0;-}--static int commit_parse(git_revwalk *walk, commit_object *commit)-{- git_odb_object *obj;- int error;-- if (commit->parsed)- return 0;-- if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)- return error;-- if (obj->raw.type != GIT_OBJ_COMMIT) {- git_odb_object_free(obj);- giterr_set(GITERR_INVALID, "Failed to parse commit. Object is no commit object");- return -1;- }-- error = commit_quick_parse(walk, commit, &obj->raw);- git_odb_object_free(obj);- return error;-}--static int interesting(git_pqueue *list)-{- unsigned int i;- for (i = 1; i < git_pqueue_size(list); i++) {- commit_object *commit = list->d[i];- if ((commit->flags & STALE) == 0)- return 1;- }-- return 0;-}--static int merge_bases_many(commit_list **out, git_revwalk *walk, commit_object *one, git_vector *twos)-{- int error;- unsigned int i;- commit_object *two;- commit_list *result = NULL, *tmp = NULL;- git_pqueue list;-- /* if the commit is repeated, we have a our merge base already */- git_vector_foreach(twos, i, two) {- if (one == two)- return commit_list_insert(one, out) ? 0 : -1;- }-- if (git_pqueue_init(&list, twos->length * 2, commit_time_cmp) < 0)- return -1;-- if (commit_parse(walk, one) < 0)- return -1;-- one->flags |= PARENT1;- if (git_pqueue_insert(&list, one) < 0)- return -1;-- git_vector_foreach(twos, i, two) {- commit_parse(walk, two);- two->flags |= PARENT2;- if (git_pqueue_insert(&list, two) < 0)- return -1;- }-- /* as long as there are non-STALE commits */- while (interesting(&list)) {- commit_object *commit;- int flags;-- commit = git_pqueue_pop(&list);-- flags = commit->flags & (PARENT1 | PARENT2 | STALE);- if (flags == (PARENT1 | PARENT2)) {- if (!(commit->flags & RESULT)) {- commit->flags |= RESULT;- if (commit_list_insert(commit, &result) == NULL)- return -1;- }- /* we mark the parents of a merge stale */- flags |= STALE;- }-- for (i = 0; i < commit->out_degree; i++) {- commit_object *p = commit->parents[i];- if ((p->flags & flags) == flags)- continue;-- if ((error = commit_parse(walk, p)) < 0)- return error;-- p->flags |= flags;- if (git_pqueue_insert(&list, p) < 0)- return -1;- }- }-- git_pqueue_free(&list);-- /* filter out any stale commits in the results */- tmp = result;- result = NULL;-- while (tmp) {- struct commit_list *next = tmp->next;- if (!(tmp->item->flags & STALE))- if (commit_list_insert_by_date(tmp->item, &result) == NULL)- return -1;-- git__free(tmp);- tmp = next;- }-- *out = result;- return 0;-}--int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two)-{- git_revwalk *walk;- git_vector list;- commit_list *result = NULL;- commit_object *commit;- void *contents[1];-- if (git_revwalk_new(&walk, repo) < 0)- return -1;-- commit = commit_lookup(walk, two);- if (commit == NULL)- goto on_error;-- /* This is just one value, so we can do it on the stack */- memset(&list, 0x0, sizeof(git_vector));- contents[0] = commit;- list.length = 1;- list.contents = contents;-- commit = commit_lookup(walk, one);- if (commit == NULL)- goto on_error;-- if (merge_bases_many(&result, walk, commit, &list) < 0)- goto on_error;-- if (!result) {- git_revwalk_free(walk);- return GIT_ENOTFOUND;- }-- git_oid_cpy(out, &result->item->oid);- commit_list_free(&result);- git_revwalk_free(walk);-- return 0;--on_error:- git_revwalk_free(walk);- return -1;-}--static void mark_uninteresting(commit_object *commit)+static void mark_uninteresting(git_commit_list_node *commit) { unsigned short i; assert(commit);@@ -405,7 +57,7 @@ mark_uninteresting(commit->parents[i]); } -static int process_commit(git_revwalk *walk, commit_object *commit, int hide)+static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int hide) { int error; @@ -417,13 +69,13 @@ commit->seen = 1; - if ((error = commit_parse(walk, commit)) < 0)+ if ((error = git_commit_list_parse(walk, commit)) < 0) return error; return walk->enqueue(walk, commit); } -static int process_commit_parents(git_revwalk *walk, commit_object *commit)+static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commit) { unsigned short i; int error = 0;@@ -436,9 +88,22 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) {- commit_object *commit;+ git_object *obj;+ git_otype type;+ git_commit_list_node *commit; - commit = commit_lookup(walk, oid);+ if (git_object_lookup(&obj, walk->repo, oid, GIT_OBJ_ANY) < 0)+ return -1;++ type = git_object_type(obj);+ git_object_free(obj);++ if (type != GIT_OBJ_COMMIT) {+ giterr_set(GITERR_INVALID, "Object is no commit object");+ return -1;+ }++ commit = git_revwalk__commit_lookup(walk, oid); if (commit == NULL) return -1; /* error already reported by failed lookup */ @@ -470,7 +135,7 @@ { git_oid oid; - if (git_reference_name_to_oid(&oid, walk->repo, refname) < 0)+ if (git_reference_name_to_id(&oid, walk->repo, refname) < 0) return -1; return push_commit(walk, &oid, hide);@@ -478,7 +143,6 @@ struct push_cb_data { git_revwalk *walk;- const char *glob; int hide; }; @@ -486,10 +150,7 @@ { struct push_cb_data *data = (struct push_cb_data *)data_; - if (!p_fnmatch(data->glob, refname, 0))- return push_ref(data->walk, refname, data->hide);-- return 0;+ return push_ref(data->walk, refname, data->hide); } static int push_glob(git_revwalk *walk, const char *glob, int hide)@@ -522,11 +183,10 @@ goto on_error; data.walk = walk;- data.glob = git_buf_cstr(&buf); data.hide = hide; - if (git_reference_foreach(- walk->repo, GIT_REF_LISTALL, push_glob_cb, &data) < 0)+ if (git_reference_foreach_glob(+ walk->repo, git_buf_cstr(&buf), GIT_REF_LISTALL, push_glob_cb, &data) < 0) goto on_error; regfree(&preg);@@ -569,26 +229,51 @@ return push_ref(walk, refname, 0); } +int git_revwalk_push_range(git_revwalk *walk, const char *range)+{+ git_revspec revspec;+ int error = 0;++ if ((error = git_revparse(&revspec, walk->repo, range)))+ return error;++ if (revspec.flags & GIT_REVPARSE_MERGE_BASE) {+ /* TODO: support "<commit>...<commit>" */+ giterr_set(GITERR_INVALID, "Symmetric differences not implemented in revwalk");+ return GIT_EINVALIDSPEC;+ }++ if ((error = push_commit(walk, git_object_id(revspec.from), 1)))+ goto out;++ error = push_commit(walk, git_object_id(revspec.to), 0);++out:+ git_object_free(revspec.from);+ git_object_free(revspec.to);+ return error;+}+ int git_revwalk_hide_ref(git_revwalk *walk, const char *refname) { assert(walk && refname); return push_ref(walk, refname, 1); } -static int revwalk_enqueue_timesort(git_revwalk *walk, commit_object *commit)+static int revwalk_enqueue_timesort(git_revwalk *walk, git_commit_list_node *commit) { return git_pqueue_insert(&walk->iterator_time, commit); } -static int revwalk_enqueue_unsorted(git_revwalk *walk, commit_object *commit)+static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *commit) {- return commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1;+ return git_commit_list_insert(commit, &walk->iterator_rand) ? 0 : -1; } -static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk)+static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk) { int error;- commit_object *next;+ git_commit_list_node *next; while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) { if ((error = process_commit_parents(walk, next)) < 0)@@ -600,15 +285,16 @@ } } - return GIT_REVWALKOVER;+ giterr_clear();+ return GIT_ITEROVER; } -static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk)+static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk) { int error;- commit_object *next;+ git_commit_list_node *next; - while ((next = commit_list_pop(&walk->iterator_rand)) != NULL) {+ while ((next = git_commit_list_pop(&walk->iterator_rand)) != NULL) { if ((error = process_commit_parents(walk, next)) < 0) return error; @@ -618,18 +304,21 @@ } } - return GIT_REVWALKOVER;+ giterr_clear();+ return GIT_ITEROVER; } -static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk)+static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk) {- commit_object *next;+ git_commit_list_node *next; unsigned short i; for (;;) {- next = commit_list_pop(&walk->iterator_topo);- if (next == NULL)- return GIT_REVWALKOVER;+ next = git_commit_list_pop(&walk->iterator_topo);+ if (next == NULL) {+ giterr_clear();+ return GIT_ITEROVER;+ } if (next->in_degree > 0) { next->topo_delay = 1;@@ -637,11 +326,11 @@ } for (i = 0; i < next->out_degree; ++i) {- commit_object *parent = next->parents[i];+ git_commit_list_node *parent = next->parents[i]; if (--parent->in_degree == 0 && parent->topo_delay) { parent->topo_delay = 0;- if (commit_list_insert(parent, &walk->iterator_topo) == NULL)+ if (git_commit_list_insert(parent, &walk->iterator_topo) == NULL) return -1; } }@@ -651,10 +340,10 @@ } } -static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk)+static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk) {- *object_out = commit_list_pop(&walk->iterator_reverse);- return *object_out ? 0 : GIT_REVWALKOVER;+ *object_out = git_commit_list_pop(&walk->iterator_reverse);+ return *object_out ? 0 : GIT_ITEROVER; } @@ -662,21 +351,23 @@ { int error; unsigned int i;- commit_object *next, *two;- commit_list *bases = NULL;+ git_commit_list_node *next, *two;+ git_commit_list *bases = NULL; /* * If walk->one is NULL, there were no positive references, * so we know that the walk is already over. */- if (walk->one == NULL)- return GIT_REVWALKOVER;+ if (walk->one == NULL) {+ giterr_clear();+ return GIT_ITEROVER;+ } /* first figure out what the merge bases are */- if (merge_bases_many(&bases, walk, walk->one, &walk->twos) < 0)+ if (git_merge__bases_many(&bases, walk, walk->one, &walk->twos) < 0) return -1; - commit_list_free(&bases);+ git_commit_list_free(&bases); if (process_commit(walk, walk->one, walk->one->uninteresting) < 0) return -1; @@ -690,15 +381,15 @@ while ((error = walk->get_next(&next, walk)) == 0) { for (i = 0; i < next->out_degree; ++i) {- commit_object *parent = next->parents[i];+ git_commit_list_node *parent = next->parents[i]; parent->in_degree++; } - if (commit_list_insert(next, &walk->iterator_topo) == NULL)+ if (git_commit_list_insert(next, &walk->iterator_topo) == NULL) return -1; } - if (error != GIT_REVWALKOVER)+ if (error != GIT_ITEROVER) return error; walk->get_next = &revwalk_next_toposort;@@ -707,10 +398,10 @@ if (walk->sorting & GIT_SORT_REVERSE) { while ((error = walk->get_next(&next, walk)) == 0)- if (commit_list_insert(next, &walk->iterator_reverse) == NULL)+ if (git_commit_list_insert(next, &walk->iterator_reverse) == NULL) return -1; - if (error != GIT_REVWALKOVER)+ if (error != GIT_ITEROVER) return error; walk->get_next = &revwalk_next_reverse;@@ -721,9 +412,6 @@ } --- int git_revwalk_new(git_revwalk **revwalk_out, git_repository *repo) { git_revwalk *walk;@@ -736,7 +424,7 @@ walk->commits = git_oidmap_alloc(); GITERR_CHECK_ALLOC(walk->commits); - if (git_pqueue_init(&walk->iterator_time, 8, commit_time_cmp) < 0 ||+ if (git_pqueue_init(&walk->iterator_time, 8, git_commit_list_time_cmp) < 0 || git_vector_init(&walk->twos, 4, NULL) < 0 || git_pool_init(&walk->commit_pool, 1, git_pool__suggest_items_per_page(COMMIT_ALLOC) * COMMIT_ALLOC) < 0)@@ -798,7 +486,7 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) { int error;- commit_object *next;+ git_commit_list_node *next; assert(walk && oid); @@ -809,9 +497,10 @@ error = walk->get_next(&next, walk); - if (error == GIT_REVWALKOVER) {+ if (error == GIT_ITEROVER) { git_revwalk_reset(walk);- return GIT_REVWALKOVER;+ giterr_clear();+ return GIT_ITEROVER; } if (!error)@@ -822,7 +511,7 @@ void git_revwalk_reset(git_revwalk *walk) {- commit_object *commit;+ git_commit_list_node *commit; assert(walk); @@ -834,9 +523,9 @@ }); git_pqueue_clear(&walk->iterator_time);- commit_list_free(&walk->iterator_topo);- commit_list_free(&walk->iterator_rand);- commit_list_free(&walk->iterator_reverse);+ git_commit_list_free(&walk->iterator_topo);+ git_commit_list_free(&walk->iterator_rand);+ git_commit_list_free(&walk->iterator_reverse); walk->walking = 0; walk->one = NULL;
+ libgit2/src/revwalk.h view
@@ -0,0 +1,44 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_revwalk_h__+#define INCLUDE_revwalk_h__++#include "git2/revwalk.h"+#include "oidmap.h"+#include "commit_list.h"+#include "pqueue.h"+#include "pool.h"+#include "vector.h"++GIT__USE_OIDMAP;++struct git_revwalk {+ git_repository *repo;+ git_odb *odb;++ git_oidmap *commits;+ git_pool commit_pool;++ git_commit_list *iterator_topo;+ git_commit_list *iterator_rand;+ git_commit_list *iterator_reverse;+ git_pqueue iterator_time;++ int (*get_next)(git_commit_list_node **, git_revwalk *);+ int (*enqueue)(git_revwalk *, git_commit_list_node *);++ unsigned walking:1;+ unsigned int sorting;++ /* merge base calculation */+ git_commit_list_node *one;+ git_vector twos;+};++git_commit_list_node *git_revwalk__commit_lookup(git_revwalk *walk, const git_oid *oid);++#endif
− libgit2/src/sha1.c
@@ -1,280 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#include "common.h"-#include "sha1.h"--#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))--/*- * Force usage of rol or ror by selecting the one with the smaller constant.- * It _can_ generate slightly smaller code (a constant of 1 is special), but- * perhaps more importantly it's possibly faster on any uarch that does a- * rotate with a loop.- */--#define SHA_ASM(op, x, n) ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; })-#define SHA_ROL(x,n) SHA_ASM("rol", x, n)-#define SHA_ROR(x,n) SHA_ASM("ror", x, n)--#else--#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r)))-#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n))-#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n)--#endif--/*- * If you have 32 registers or more, the compiler can (and should)- * try to change the array[] accesses into registers. However, on- * machines with less than ~25 registers, that won't really work,- * and at least gcc will make an unholy mess of it.- *- * So to avoid that mess which just slows things down, we force- * the stores to memory to actually happen (we might be better off- * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as- * suggested by Artur Skawina - that will also make gcc unable to- * try to do the silly "optimize away loads" part because it won't- * see what the value will be).- *- * Ben Herrenschmidt reports that on PPC, the C version comes close- * to the optimized asm with this (ie on PPC you don't want that- * 'volatile', since there are lots of registers).- *- * On ARM we get the best code generation by forcing a full memory barrier- * between each SHA_ROUND, otherwise gcc happily get wild with spilling and- * the stack frame size simply explode and performance goes down the drain.- */--#if defined(__i386__) || defined(__x86_64__)- #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val))-#elif defined(__GNUC__) && defined(__arm__)- #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0)-#else- #define setW(x, val) (W(x) = (val))-#endif--/*- * Performance might be improved if the CPU architecture is OK with- * unaligned 32-bit loads and a fast ntohl() is available.- * Otherwise fall back to byte loads and shifts which is portable,- * and is faster on architectures with memory alignment issues.- */--#if defined(__i386__) || defined(__x86_64__) || \- defined(_M_IX86) || defined(_M_X64) || \- defined(__ppc__) || defined(__ppc64__) || \- defined(__powerpc__) || defined(__powerpc64__) || \- defined(__s390__) || defined(__s390x__)--#define get_be32(p) ntohl(*(const unsigned int *)(p))-#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)--#else--#define get_be32(p) ( \- (*((const unsigned char *)(p) + 0) << 24) | \- (*((const unsigned char *)(p) + 1) << 16) | \- (*((const unsigned char *)(p) + 2) << 8) | \- (*((const unsigned char *)(p) + 3) << 0) )-#define put_be32(p, v) do { \- unsigned int __v = (v); \- *((unsigned char *)(p) + 0) = __v >> 24; \- *((unsigned char *)(p) + 1) = __v >> 16; \- *((unsigned char *)(p) + 2) = __v >> 8; \- *((unsigned char *)(p) + 3) = __v >> 0; } while (0)--#endif--/* This "rolls" over the 512-bit array */-#define W(x) (array[(x)&15])--/*- * Where do we get the source from? The first 16 iterations get it from- * the input data, the next mix it from the 512-bit array.- */-#define SHA_SRC(t) get_be32(data + t)-#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1)--#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \- unsigned int TEMP = input(t); setW(t, TEMP); \- E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \- B = SHA_ROR(B, 2); } while (0)--#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )-#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )-#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E )-#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E )-#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E )--static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data)-{- unsigned int A,B,C,D,E;- unsigned int array[16];-- A = ctx->H[0];- B = ctx->H[1];- C = ctx->H[2];- D = ctx->H[3];- E = ctx->H[4];-- /* Round 1 - iterations 0-16 take their input from 'data' */- T_0_15( 0, A, B, C, D, E);- T_0_15( 1, E, A, B, C, D);- T_0_15( 2, D, E, A, B, C);- T_0_15( 3, C, D, E, A, B);- T_0_15( 4, B, C, D, E, A);- T_0_15( 5, A, B, C, D, E);- T_0_15( 6, E, A, B, C, D);- T_0_15( 7, D, E, A, B, C);- T_0_15( 8, C, D, E, A, B);- T_0_15( 9, B, C, D, E, A);- T_0_15(10, A, B, C, D, E);- T_0_15(11, E, A, B, C, D);- T_0_15(12, D, E, A, B, C);- T_0_15(13, C, D, E, A, B);- T_0_15(14, B, C, D, E, A);- T_0_15(15, A, B, C, D, E);-- /* Round 1 - tail. Input from 512-bit mixing array */- T_16_19(16, E, A, B, C, D);- T_16_19(17, D, E, A, B, C);- T_16_19(18, C, D, E, A, B);- T_16_19(19, B, C, D, E, A);-- /* Round 2 */- T_20_39(20, A, B, C, D, E);- T_20_39(21, E, A, B, C, D);- T_20_39(22, D, E, A, B, C);- T_20_39(23, C, D, E, A, B);- T_20_39(24, B, C, D, E, A);- T_20_39(25, A, B, C, D, E);- T_20_39(26, E, A, B, C, D);- T_20_39(27, D, E, A, B, C);- T_20_39(28, C, D, E, A, B);- T_20_39(29, B, C, D, E, A);- T_20_39(30, A, B, C, D, E);- T_20_39(31, E, A, B, C, D);- T_20_39(32, D, E, A, B, C);- T_20_39(33, C, D, E, A, B);- T_20_39(34, B, C, D, E, A);- T_20_39(35, A, B, C, D, E);- T_20_39(36, E, A, B, C, D);- T_20_39(37, D, E, A, B, C);- T_20_39(38, C, D, E, A, B);- T_20_39(39, B, C, D, E, A);-- /* Round 3 */- T_40_59(40, A, B, C, D, E);- T_40_59(41, E, A, B, C, D);- T_40_59(42, D, E, A, B, C);- T_40_59(43, C, D, E, A, B);- T_40_59(44, B, C, D, E, A);- T_40_59(45, A, B, C, D, E);- T_40_59(46, E, A, B, C, D);- T_40_59(47, D, E, A, B, C);- T_40_59(48, C, D, E, A, B);- T_40_59(49, B, C, D, E, A);- T_40_59(50, A, B, C, D, E);- T_40_59(51, E, A, B, C, D);- T_40_59(52, D, E, A, B, C);- T_40_59(53, C, D, E, A, B);- T_40_59(54, B, C, D, E, A);- T_40_59(55, A, B, C, D, E);- T_40_59(56, E, A, B, C, D);- T_40_59(57, D, E, A, B, C);- T_40_59(58, C, D, E, A, B);- T_40_59(59, B, C, D, E, A);-- /* Round 4 */- T_60_79(60, A, B, C, D, E);- T_60_79(61, E, A, B, C, D);- T_60_79(62, D, E, A, B, C);- T_60_79(63, C, D, E, A, B);- T_60_79(64, B, C, D, E, A);- T_60_79(65, A, B, C, D, E);- T_60_79(66, E, A, B, C, D);- T_60_79(67, D, E, A, B, C);- T_60_79(68, C, D, E, A, B);- T_60_79(69, B, C, D, E, A);- T_60_79(70, A, B, C, D, E);- T_60_79(71, E, A, B, C, D);- T_60_79(72, D, E, A, B, C);- T_60_79(73, C, D, E, A, B);- T_60_79(74, B, C, D, E, A);- T_60_79(75, A, B, C, D, E);- T_60_79(76, E, A, B, C, D);- T_60_79(77, D, E, A, B, C);- T_60_79(78, C, D, E, A, B);- T_60_79(79, B, C, D, E, A);-- ctx->H[0] += A;- ctx->H[1] += B;- ctx->H[2] += C;- ctx->H[3] += D;- ctx->H[4] += E;-}--void git__blk_SHA1_Init(blk_SHA_CTX *ctx)-{- ctx->size = 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;-}--void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len)-{- unsigned int lenW = ctx->size & 63;-- ctx->size += len;-- /* Read the data into W and process blocks as they get full */- if (lenW) {- unsigned int left = 64 - lenW;- if (len < left)- left = (unsigned int)len;- memcpy(lenW + (char *)ctx->W, data, left);- lenW = (lenW + left) & 63;- len -= left;- data = ((const char *)data + left);- if (lenW)- return;- blk_SHA1_Block(ctx, ctx->W);- }- while (len >= 64) {- blk_SHA1_Block(ctx, data);- data = ((const char *)data + 64);- len -= 64;- }- if (len)- memcpy(ctx->W, data, len);-}--void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx)-{- static const unsigned char pad[64] = { 0x80 };- unsigned int padlen[2];- int i;-- /* Pad with a binary 1 (ie 0x80), then zeroes, then length */- padlen[0] = htonl((uint32_t)(ctx->size >> 29));- padlen[1] = htonl((uint32_t)(ctx->size << 3));-- i = ctx->size & 63;- git__blk_SHA1_Update(ctx, pad, 1+ (63 & (55 - i)));- git__blk_SHA1_Update(ctx, padlen, 8);-- /* Output hash */- for (i = 0; i < 5; i++)- put_be32(hashout + i*4, ctx->H[i]);-}
− libgit2/src/sha1.h
@@ -1,21 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--typedef struct {- unsigned long long size;- unsigned int H[5];- unsigned int W[16];-} blk_SHA_CTX;--void git__blk_SHA1_Init(blk_SHA_CTX *ctx);-void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, size_t len);-void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx);--#define SHA_CTX blk_SHA_CTX-#define SHA1_Init git__blk_SHA1_Init-#define SHA1_Update git__blk_SHA1_Update-#define SHA1_Final git__blk_SHA1_Final
libgit2/src/sha1_lookup.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/sha1_lookup.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/signature.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -22,80 +22,60 @@ git__free(sig); } -static const char *skip_leading_spaces(const char *buffer, const char *buffer_end)-{- while (*buffer == ' ' && buffer < buffer_end)- buffer++;-- return buffer;-}--static const char *skip_trailing_spaces(const char *buffer_start, const char *buffer_end)-{- while (*buffer_end == ' ' && buffer_end > buffer_start)- buffer_end--;-- return buffer_end;-}- static int signature_error(const char *msg) { giterr_set(GITERR_INVALID, "Failed to parse signature - %s", msg); return -1; } -static int process_trimming(const char *input, char **storage, const char *input_end, int fail_when_empty)+static bool contains_angle_brackets(const char *input) {- const char *left, *right;- size_t trimmed_input_length;-- assert(storage);-- left = skip_leading_spaces(input, input_end);- right = skip_trailing_spaces(input, input_end - 1);-- if (right < left) {- if (fail_when_empty)- return signature_error("input is either empty of contains only spaces");+ return strchr(input, '<') != NULL || strchr(input, '>') != NULL;+} - right = left - 1;+static char *extract_trimmed(const char *ptr, size_t len)+{+ while (len && ptr[0] == ' ') {+ ptr++; len--; } - trimmed_input_length = right - left + 1;-- *storage = git__malloc(trimmed_input_length + 1);- GITERR_CHECK_ALLOC(*storage);-- memcpy(*storage, left, trimmed_input_length);- (*storage)[trimmed_input_length] = 0;+ while (len && ptr[len - 1] == ' ') {+ len--;+ } - return 0;+ return git__substrdup(ptr, len); } int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) {- int error; git_signature *p = NULL; assert(name && email); *sig_out = NULL; + if (contains_angle_brackets(name) ||+ contains_angle_brackets(email)) {+ return signature_error(+ "Neither `name` nor `email` should contain angle brackets chars.");+ }+ p = git__calloc(1, sizeof(git_signature)); GITERR_CHECK_ALLOC(p); - if ((error = process_trimming(name, &p->name, name + strlen(name), 1)) < 0 ||- (error = process_trimming(email, &p->email, email + strlen(email), 1)) < 0)- {+ p->name = extract_trimmed(name, strlen(name));+ p->email = extract_trimmed(email, strlen(email));++ if (p->name == NULL || p->email == NULL ||+ p->name[0] == '\0' || p->email[0] == '\0') { git_signature_free(p);- return error;+ return -1; }-+ p->when.time = time; p->when.offset = offset; *sig_out = p;- return 0; } @@ -111,36 +91,26 @@ { time_t now; time_t offset;- struct tm *utc_tm, *local_tm;+ struct tm *utc_tm; git_signature *sig;--#ifndef GIT_WIN32- struct tm _utc, _local;-#endif+ struct tm _utc; *sig_out = NULL; - time(&now);-- /**- * On Win32, `gmtime_r` doesn't exist but- * `gmtime` is threadsafe, so we can use that+ /*+ * Get the current time as seconds since the epoch and+ * transform that into a tm struct containing the time at+ * UTC. Give that to mktime which considers it a local time+ * (tm_isdst = -1 asks it to take DST into account) and gives+ * us that time as seconds since the epoch. The difference+ * between its return value and 'now' is our offset to UTC. */-#ifdef GIT_WIN32- utc_tm = gmtime(&now);- local_tm = localtime(&now);-#else- utc_tm = gmtime_r(&now, &_utc);- local_tm = localtime_r(&now, &_local);-#endif-- offset = mktime(local_tm) - mktime(utc_tm);+ time(&now);+ utc_tm = p_gmtime_r(&now, &_utc);+ utc_tm->tm_isdst = -1;+ offset = (time_t)difftime(now, mktime(utc_tm)); offset /= 60; - /* mktime takes care of setting tm_isdst correctly */- if (local_tm->tm_isdst)- offset += 60;- if (git_signature_new(&sig, name, email, now, (int)offset) < 0) return -1; @@ -149,169 +119,71 @@ return 0; } -static int timezone_error(const char *msg)-{- giterr_set(GITERR_INVALID, "Failed to parse TZ offset - %s", msg);- return -1;-}--static int parse_timezone_offset(const char *buffer, int *offset_out)-{- int dec_offset;- int mins, hours, offset;-- const char *offset_start;- const char *offset_end;-- offset_start = buffer;-- if (*offset_start == '\n') {- *offset_out = 0;- return 0;- }-- if (offset_start[0] != '-' && offset_start[0] != '+')- return timezone_error("does not start with '+' or '-'");-- if (offset_start[1] < '0' || offset_start[1] > '9')- return timezone_error("expected initial digit");-- if (git__strtol32(&dec_offset, offset_start + 1, &offset_end, 10) < 0)- return timezone_error("not a valid number");-- if (offset_end - offset_start != 5)- return timezone_error("invalid length");-- if (dec_offset > 1400)- return timezone_error("value too large");-- hours = dec_offset / 100;- mins = dec_offset % 100;-- if (hours > 14) // see http://www.worldtimezone.com/faq.html- return timezone_error("hour value too large");-- if (mins > 59)- return timezone_error("minutes value too large");-- offset = (hours * 60) + mins;-- if (offset_start[0] == '-')- offset *= -1;-- *offset_out = offset;-- return 0;-}--static int process_next_token(const char **buffer_out, char **storage,- const char *token_end, const char *right_boundary)-{- int error = process_trimming(*buffer_out, storage, token_end, 0);- if (error < 0)- return error;-- *buffer_out = token_end + 1;-- if (*buffer_out > right_boundary)- return signature_error("signature is too short");-- return 0;-}--static const char *scan_for_previous_token(const char *buffer, const char *left_boundary)-{- const char *start;-- if (buffer <= left_boundary)- return NULL;-- start = skip_trailing_spaces(left_boundary, buffer);-- /* Search for previous occurence of space */- while (start[-1] != ' ' && start > left_boundary)- start--;-- return start;-}--static int parse_time(git_time_t *time_out, const char *buffer)-{- int time;- int error;-- if (*buffer == '+' || *buffer == '-') {- giterr_set(GITERR_INVALID, "Failed while parsing time. '%s' actually looks like a timezone offset.", buffer);- return -1;- }-- error = git__strtol32(&time, buffer, &buffer, 10);-- if (!error)- *time_out = (git_time_t)time;-- return error;-}- int git_signature__parse(git_signature *sig, const char **buffer_out, const char *buffer_end, const char *header, char ender) { const char *buffer = *buffer_out;- const char *line_end, *name_end, *email_end, *tz_start, *time_start;- int error = 0;+ const char *email_start, *email_end; - memset(sig, 0x0, sizeof(git_signature));+ memset(sig, 0, sizeof(git_signature)); - if ((line_end = memchr(buffer, ender, buffer_end - buffer)) == NULL)+ if ((buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) return signature_error("no newline given"); if (header) { const size_t header_len = strlen(header); - if (memcmp(buffer, header, header_len) != 0)+ if (buffer + header_len >= buffer_end || memcmp(buffer, header, header_len) != 0) return signature_error("expected prefix doesn't match actual"); buffer += header_len; } - if (buffer > line_end)- return signature_error("signature too short");+ email_start = git__memrchr(buffer, '<', buffer_end - buffer);+ email_end = git__memrchr(buffer, '>', buffer_end - buffer); - if ((name_end = strchr(buffer, '<')) == NULL)- return signature_error("character '<' not allowed in signature");+ if (!email_start || !email_end || email_end <= email_start)+ return signature_error("malformed e-mail"); - if ((email_end = strchr(name_end, '>')) == NULL)- return signature_error("character '>' not allowed in signature");+ email_start += 1;+ sig->name = extract_trimmed(buffer, email_start - buffer - 1);+ sig->email = extract_trimmed(email_start, email_end - email_start); - if (email_end < name_end)- return signature_error("malformed e-mail");+ /* Do we even have a time at the end of the signature? */+ if (email_end + 2 < buffer_end) {+ const char *time_start = email_end + 2;+ const char *time_end; - error = process_next_token(&buffer, &sig->name, name_end, line_end);- if (error < 0)- return error;+ if (git__strtol64(&sig->when.time, time_start, &time_end, 10) < 0)+ return signature_error("invalid Unix timestamp"); - error = process_next_token(&buffer, &sig->email, email_end, line_end);- if (error < 0)- return error;+ /* do we have a timezone? */+ if (time_end + 1 < buffer_end) {+ int offset, hours, mins;+ const char *tz_start, *tz_end; - tz_start = scan_for_previous_token(line_end - 1, buffer);+ tz_start = time_end + 1; - if (tz_start == NULL)- goto clean_exit; /* No timezone nor date */+ if ((tz_start[0] != '-' && tz_start[0] != '+') || + git__strtol32(&offset, tz_start + 1, &tz_end, 10) < 0)+ return signature_error("malformed timezone"); - time_start = scan_for_previous_token(tz_start - 1, buffer);- if (time_start == NULL || parse_time(&sig->when.time, time_start) < 0) {- /* The tz_start might point at the time */- parse_time(&sig->when.time, tz_start);- goto clean_exit;- }+ hours = offset / 100;+ mins = offset % 100; - if (parse_timezone_offset(tz_start, &sig->when.offset) < 0) {- sig->when.time = 0; /* Bogus timezone, we reset the time */+ /*+ * only store timezone if it's not overflowing;+ * see http://www.worldtimezone.com/faq.html+ */+ if (hours < 14 && mins < 59) {+ sig->when.offset = (hours * 60) + mins;+ if (tz_start[0] == '-')+ sig->when.offset = -sig->when.offset;+ }+ } } -clean_exit:- *buffer_out = line_end + 1;+ *buffer_out = buffer_end + 1; return 0; }
libgit2/src/signature.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
+ libgit2/src/stash.c view
@@ -0,0 +1,663 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "common.h"+#include "repository.h"+#include "commit.h"+#include "tree.h"+#include "reflog.h"+#include "git2/diff.h"+#include "git2/stash.h"+#include "git2/status.h"+#include "git2/checkout.h"+#include "signature.h"++static int create_error(int error, const char *msg)+{+ giterr_set(GITERR_STASH, "Cannot stash changes - %s", msg);+ return error;+}++static int retrieve_head(git_reference **out, git_repository *repo)+{+ int error = git_repository_head(out, repo);++ if (error == GIT_EORPHANEDHEAD)+ return create_error(error, "You do not have the initial commit yet.");++ return error;+}++static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit)+{+ char *formatted_oid;++ formatted_oid = git_oid_allocfmt(b_commit);+ GITERR_CHECK_ALLOC(formatted_oid);++ git_buf_put(out, formatted_oid, 7);+ git__free(formatted_oid);++ return git_buf_oom(out) ? -1 : 0;+}++static int append_commit_description(git_buf *out, git_commit* commit)+{+ const char *message;+ size_t pos = 0, len;++ if (append_abbreviated_oid(out, git_commit_id(commit)) < 0)+ return -1;++ message = git_commit_message(commit);+ len = strlen(message);++ /* TODO: Replace with proper commit short message+ * when git_commit_message_short() is implemented.+ */+ while (pos < len && message[pos] != '\n')+ pos++;++ git_buf_putc(out, ' ');+ git_buf_put(out, message, pos);+ git_buf_putc(out, '\n');++ return git_buf_oom(out) ? -1 : 0;+}++static int retrieve_base_commit_and_message(+ git_commit **b_commit,+ git_buf *stash_message,+ git_repository *repo)+{+ git_reference *head = NULL;+ int error;++ if ((error = retrieve_head(&head, repo)) < 0)+ return error;++ if (strcmp("HEAD", git_reference_name(head)) == 0)+ error = git_buf_puts(stash_message, "(no branch): ");+ else+ error = git_buf_printf(+ stash_message,+ "%s: ",+ git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR));+ if (error < 0)+ goto cleanup;++ if ((error = git_commit_lookup(+ b_commit, repo, git_reference_target(head))) < 0)+ goto cleanup;++ if ((error = append_commit_description(stash_message, *b_commit)) < 0)+ goto cleanup;++cleanup:+ git_reference_free(head);+ return error;+}++static int build_tree_from_index(git_tree **out, git_index *index)+{+ int error;+ git_oid i_tree_oid;++ if ((error = git_index_write_tree(&i_tree_oid, index)) < 0)+ return -1;++ return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);+}++static int commit_index(+ git_commit **i_commit,+ git_index *index,+ git_signature *stasher,+ const char *message,+ const git_commit *parent)+{+ git_tree *i_tree = NULL;+ git_oid i_commit_oid;+ git_buf msg = GIT_BUF_INIT;+ int error;++ if ((error = build_tree_from_index(&i_tree, index)) < 0)+ goto cleanup;++ if ((error = git_buf_printf(&msg, "index on %s\n", message)) < 0)+ goto cleanup;++ if ((error = git_commit_create(+ &i_commit_oid,+ git_index_owner(index),+ NULL,+ stasher,+ stasher,+ NULL,+ git_buf_cstr(&msg),+ i_tree,+ 1,+ &parent)) < 0)+ goto cleanup;++ error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid);++cleanup:+ git_tree_free(i_tree);+ git_buf_free(&msg);+ return error;+}++struct cb_data {+ git_index *index;++ int error;++ bool include_changed;+ bool include_untracked;+ bool include_ignored;+};++static int update_index_cb(+ const git_diff_delta *delta,+ float progress,+ void *payload)+{+ struct cb_data *data = (struct cb_data *)payload;+ const char *add_path = NULL;++ GIT_UNUSED(progress);++ switch (delta->status) {+ case GIT_DELTA_IGNORED:+ if (data->include_ignored)+ add_path = delta->new_file.path;+ break;++ case GIT_DELTA_UNTRACKED:+ if (data->include_untracked)+ add_path = delta->new_file.path;+ break;++ case GIT_DELTA_ADDED:+ case GIT_DELTA_MODIFIED:+ if (data->include_changed)+ add_path = delta->new_file.path;+ break;++ case GIT_DELTA_DELETED:+ if (!data->include_changed)+ break;+ if (git_index_find(NULL, data->index, delta->old_file.path) == 0)+ data->error = git_index_remove(+ data->index, delta->old_file.path, 0);+ break;++ default:+ /* Unimplemented */+ giterr_set(+ GITERR_INVALID,+ "Cannot update index. Unimplemented status (%d)",+ delta->status);+ data->error = -1;+ break;+ }++ if (add_path != NULL)+ data->error = git_index_add_bypath(data->index, add_path);++ return data->error;+}++static int build_untracked_tree(+ git_tree **tree_out,+ git_index *index,+ git_commit *i_commit,+ uint32_t flags)+{+ git_tree *i_tree = NULL;+ git_diff_list *diff = NULL;+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;+ struct cb_data data = {0};+ int error;++ git_index_clear(index);++ data.index = index;++ if (flags & GIT_STASH_INCLUDE_UNTRACKED) {+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED |+ GIT_DIFF_RECURSE_UNTRACKED_DIRS;+ data.include_untracked = true;+ }++ if (flags & GIT_STASH_INCLUDE_IGNORED) {+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED;+ data.include_ignored = true;+ }++ if ((error = git_commit_tree(&i_tree, i_commit)) < 0)+ goto cleanup;++ if ((error = git_diff_tree_to_workdir(+ &diff, git_index_owner(index), i_tree, &opts)) < 0)+ goto cleanup;++ if ((error = git_diff_foreach(+ diff, update_index_cb, NULL, NULL, &data)) < 0)+ {+ if (error == GIT_EUSER)+ error = data.error;+ goto cleanup;+ }++ error = build_tree_from_index(tree_out, index);++cleanup:+ git_diff_list_free(diff);+ git_tree_free(i_tree);+ return error;+}++static int commit_untracked(+ git_commit **u_commit,+ git_index *index,+ git_signature *stasher,+ const char *message,+ git_commit *i_commit,+ uint32_t flags)+{+ git_tree *u_tree = NULL;+ git_oid u_commit_oid;+ git_buf msg = GIT_BUF_INIT;+ int error;++ if ((error = build_untracked_tree(&u_tree, index, i_commit, flags)) < 0)+ goto cleanup;++ if ((error = git_buf_printf(&msg, "untracked files on %s\n", message)) < 0)+ goto cleanup;++ if ((error = git_commit_create(+ &u_commit_oid,+ git_index_owner(index),+ NULL,+ stasher,+ stasher,+ NULL,+ git_buf_cstr(&msg),+ u_tree,+ 0,+ NULL)) < 0)+ goto cleanup;++ error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid);++cleanup:+ git_tree_free(u_tree);+ git_buf_free(&msg);+ return error;+}++static int build_workdir_tree(+ git_tree **tree_out,+ git_index *index,+ git_commit *b_commit)+{+ git_repository *repo = git_index_owner(index);+ git_tree *b_tree = NULL;+ git_diff_list *diff = NULL, *diff2 = NULL;+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;+ struct cb_data data = {0};+ int error;++ if ((error = git_commit_tree(&b_tree, b_commit)) < 0)+ goto cleanup;++ if ((error = git_diff_tree_to_index(&diff, repo, b_tree, NULL, &opts)) < 0)+ goto cleanup;++ if ((error = git_diff_index_to_workdir(&diff2, repo, NULL, &opts)) < 0)+ goto cleanup;++ if ((error = git_diff_merge(diff, diff2)) < 0)+ goto cleanup;++ data.index = index;+ data.include_changed = true;++ if ((error = git_diff_foreach(+ diff, update_index_cb, NULL, NULL, &data)) < 0)+ {+ if (error == GIT_EUSER)+ error = data.error;+ goto cleanup;+ }+++ if ((error = build_tree_from_index(tree_out, index)) < 0)+ goto cleanup;++cleanup:+ git_diff_list_free(diff);+ git_diff_list_free(diff2);+ git_tree_free(b_tree);++ return error;+}++static int commit_worktree(+ git_oid *w_commit_oid,+ git_index *index,+ git_signature *stasher,+ const char *message,+ git_commit *i_commit,+ git_commit *b_commit,+ git_commit *u_commit)+{+ int error = 0;+ git_tree *w_tree = NULL, *i_tree = NULL;+ const git_commit *parents[] = { NULL, NULL, NULL };++ parents[0] = b_commit;+ parents[1] = i_commit;+ parents[2] = u_commit;++ if ((error = git_commit_tree(&i_tree, i_commit)) < 0)+ goto cleanup;++ if ((error = git_index_read_tree(index, i_tree)) < 0)+ goto cleanup;++ if ((error = build_workdir_tree(&w_tree, index, b_commit)) < 0)+ goto cleanup;++ error = git_commit_create(+ w_commit_oid,+ git_index_owner(index),+ NULL,+ stasher,+ stasher,+ NULL,+ message,+ w_tree,+ u_commit ? 3 : 2,+ parents);++cleanup:+ git_tree_free(i_tree);+ git_tree_free(w_tree);+ return error;+}++static int prepare_worktree_commit_message(+ git_buf* msg,+ const char *user_message)+{+ git_buf buf = GIT_BUF_INIT;+ int error;++ if ((error = git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg))) < 0)+ return error;++ git_buf_clear(msg);++ if (!user_message)+ git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf));+ else {+ const char *colon;++ if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL)+ goto cleanup;++ git_buf_puts(msg, "On ");+ git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr);+ git_buf_printf(msg, ": %s\n", user_message);+ }++ error = (git_buf_oom(msg) || git_buf_oom(&buf)) ? -1 : 0;++cleanup:+ git_buf_free(&buf);++ return error;+}++static int update_reflog(+ git_oid *w_commit_oid,+ git_repository *repo,+ git_signature *stasher,+ const char *message)+{+ git_reference *stash = NULL;+ git_reflog *reflog = NULL;+ int error;++ if ((error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1)) < 0)+ goto cleanup;++ if ((error = git_reflog_read(&reflog, stash)) < 0)+ goto cleanup;++ if ((error = git_reflog_append(reflog, w_commit_oid, stasher, message)) < 0)+ goto cleanup;++ if ((error = git_reflog_write(reflog)) < 0)+ goto cleanup;++cleanup:+ git_reference_free(stash);+ git_reflog_free(reflog);+ return error;+}++static int is_dirty_cb(const char *path, unsigned int status, void *payload)+{+ GIT_UNUSED(path);+ GIT_UNUSED(status);+ GIT_UNUSED(payload);++ return 1;+}++static int ensure_there_are_changes_to_stash(+ git_repository *repo,+ bool include_untracked_files,+ bool include_ignored_files)+{+ int error;+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;++ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;+ if (include_untracked_files)+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;++ if (include_ignored_files)+ opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED;++ error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL);++ if (error == GIT_EUSER)+ return 0;++ if (!error)+ return create_error(GIT_ENOTFOUND, "There is nothing to stash.");++ return error;+}++static int reset_index_and_workdir(+ git_repository *repo,+ git_commit *commit,+ bool remove_untracked)+{+ git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;++ opts.checkout_strategy = GIT_CHECKOUT_FORCE;++ if (remove_untracked)+ opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;++ return git_checkout_tree(repo, (git_object *)commit, &opts);+}++int git_stash_save(+ git_oid *out,+ git_repository *repo,+ git_signature *stasher,+ const char *message,+ uint32_t flags)+{+ git_index *index = NULL;+ git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL;+ git_buf msg = GIT_BUF_INIT;+ int error;++ assert(out && repo && stasher);++ if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0)+ return error;++ if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0)+ goto cleanup;++ if ((error = ensure_there_are_changes_to_stash(+ repo,+ (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0,+ (flags & GIT_STASH_INCLUDE_IGNORED) != 0)) < 0)+ goto cleanup;++ if ((error = git_repository_index(&index, repo)) < 0)+ goto cleanup;++ if ((error = commit_index(+ &i_commit, index, stasher, git_buf_cstr(&msg), b_commit)) < 0)+ goto cleanup;++ if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) &&+ (error = commit_untracked(+ &u_commit, index, stasher, git_buf_cstr(&msg),+ i_commit, flags)) < 0)+ goto cleanup;++ if ((error = prepare_worktree_commit_message(&msg, message)) < 0)+ goto cleanup;++ if ((error = commit_worktree(+ out, index, stasher, git_buf_cstr(&msg),+ i_commit, b_commit, u_commit)) < 0)+ goto cleanup;++ git_buf_rtrim(&msg);++ if ((error = update_reflog(out, repo, stasher, git_buf_cstr(&msg))) < 0)+ goto cleanup;++ if ((error = reset_index_and_workdir(+ repo,+ ((flags & GIT_STASH_KEEP_INDEX) != 0) ? i_commit : b_commit,+ (flags & GIT_STASH_INCLUDE_UNTRACKED) != 0)) < 0)+ goto cleanup;++cleanup:++ git_buf_free(&msg);+ git_commit_free(i_commit);+ git_commit_free(b_commit);+ git_commit_free(u_commit);+ git_index_free(index);++ return error;+}++int git_stash_foreach(+ git_repository *repo,+ git_stash_cb callback,+ void *payload)+{+ git_reference *stash;+ git_reflog *reflog = NULL;+ int error;+ size_t i, max;+ const git_reflog_entry *entry;++ error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE);+ if (error == GIT_ENOTFOUND)+ return 0;+ if (error < 0)+ goto cleanup;++ if ((error = git_reflog_read(&reflog, stash)) < 0)+ goto cleanup;++ max = git_reflog_entrycount(reflog);+ for (i = 0; i < max; i++) {+ entry = git_reflog_entry_byindex(reflog, i);++ if (callback(i,+ git_reflog_entry_message(entry),+ git_reflog_entry_id_new(entry),+ payload)) {+ error = GIT_EUSER;+ break;+ }+ }++cleanup:+ git_reference_free(stash);+ git_reflog_free(reflog);+ return error;+}++int git_stash_drop(+ git_repository *repo,+ size_t index)+{+ git_reference *stash;+ git_reflog *reflog = NULL;+ size_t max;+ int error;++ if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)+ return error;++ if ((error = git_reflog_read(&reflog, stash)) < 0)+ goto cleanup;++ max = git_reflog_entrycount(reflog);++ if (index > max - 1) {+ error = GIT_ENOTFOUND;+ giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);+ goto cleanup;+ }++ if ((error = git_reflog_drop(reflog, index, true)) < 0)+ goto cleanup;++ if ((error = git_reflog_write(reflog)) < 0)+ goto cleanup;++ if (max == 1) {+ error = git_reference_delete(stash);+ git_reference_free(stash);+ stash = NULL;+ } else if (index == 0) {+ const git_reflog_entry *entry;++ entry = git_reflog_entry_byindex(reflog, 0);+ + git_reference_free(stash);+ error = git_reference_create(&stash, repo, GIT_REFS_STASH_FILE, &entry->oid_cur, 1);+ }++cleanup:+ git_reference_free(stash);+ git_reflog_free(reflog);+ return error;+}
libgit2/src/status.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -17,6 +17,7 @@ #include "git2/diff.h" #include "diff.h"+#include "diff_output.h" static unsigned int index_delta2status(git_delta_t index_status) {@@ -25,7 +26,6 @@ switch (index_status) { case GIT_DELTA_ADDED: case GIT_DELTA_COPIED:- case GIT_DELTA_RENAMED: st = GIT_STATUS_INDEX_NEW; break; case GIT_DELTA_DELETED:@@ -34,6 +34,12 @@ case GIT_DELTA_MODIFIED: st = GIT_STATUS_INDEX_MODIFIED; break;+ case GIT_DELTA_RENAMED:+ st = GIT_STATUS_INDEX_RENAMED;+ break;+ case GIT_DELTA_TYPECHANGE:+ st = GIT_STATUS_INDEX_TYPECHANGE;+ break; default: break; }@@ -47,8 +53,8 @@ switch (workdir_status) { case GIT_DELTA_ADDED:- case GIT_DELTA_COPIED: case GIT_DELTA_RENAMED:+ case GIT_DELTA_COPIED: case GIT_DELTA_UNTRACKED: st = GIT_STATUS_WT_NEW; break;@@ -61,6 +67,9 @@ case GIT_DELTA_IGNORED: st = GIT_STATUS_IGNORED; break;+ case GIT_DELTA_TYPECHANGE:+ st = GIT_STATUS_WT_TYPECHANGE;+ break; default: break; }@@ -68,29 +77,76 @@ return st; } +typedef struct {+ git_status_cb cb;+ void *payload;+ const git_status_options *opts;+} status_user_callback;++static int status_invoke_cb(+ git_diff_delta *h2i, git_diff_delta *i2w, void *payload)+{+ status_user_callback *usercb = payload;+ const char *path = NULL;+ unsigned int status = 0;++ if (i2w) {+ path = i2w->old_file.path;+ status |= workdir_delta2status(i2w->status);+ }+ if (h2i) {+ path = h2i->old_file.path;+ status |= index_delta2status(h2i->status);+ }++ /* if excluding submodules and this is a submodule everywhere */+ if (usercb->opts &&+ (usercb->opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)+ {+ bool in_tree = (h2i && h2i->status != GIT_DELTA_ADDED);+ bool in_index = (h2i && h2i->status != GIT_DELTA_DELETED);+ bool in_wd = (i2w && i2w->status != GIT_DELTA_DELETED);++ if ((!in_tree || h2i->old_file.mode == GIT_FILEMODE_COMMIT) &&+ (!in_index || h2i->new_file.mode == GIT_FILEMODE_COMMIT) &&+ (!in_wd || i2w->new_file.mode == GIT_FILEMODE_COMMIT))+ return 0;+ }++ return usercb->cb(path, status, usercb->payload);+}+ int git_status_foreach_ext( git_repository *repo, const git_status_options *opts,- int (*cb)(const char *, unsigned int, void *),- void *cbdata)+ git_status_cb cb,+ void *payload) {- int err = 0, cmp;- git_diff_options diffopt;- git_diff_list *idx2head = NULL, *wd2idx = NULL;+ int err = 0;+ git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;+ git_diff_list *head2idx = NULL, *idx2wd = NULL; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;- git_diff_delta *i2h, *w2i;- unsigned int i, j, i_max, j_max;+ status_user_callback usercb; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); - if ((err = git_repository_head_tree(&head, repo)) < 0)+ GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options");++ if (show != GIT_STATUS_SHOW_INDEX_ONLY &&+ (err = git_repository__ensure_not_bare(repo, "status")) < 0) return err; - memset(&diffopt, 0, sizeof(diffopt));+ /* if there is no HEAD, that's okay - we'll make an empty iterator */+ if (((err = git_repository_head_tree(&head, repo)) < 0) &&+ !(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD))+ return err;+ memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;+ if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0)@@ -99,62 +155,58 @@ diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;- /* TODO: support EXCLUDE_SUBMODULES flag */-- if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&- (err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0)- goto cleanup;-- if (show != GIT_STATUS_SHOW_INDEX_ONLY &&- (err = git_diff_workdir_to_index(repo, &diffopt, &wd2idx)) < 0)- goto cleanup;+ if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)+ diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;+ if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0)+ diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS;+ if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)+ diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; - if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {- for (i = 0; !err && i < idx2head->deltas.length; i++) {- i2h = GIT_VECTOR_GET(&idx2head->deltas, i);- err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata);- }- git_diff_list_free(idx2head);- idx2head = NULL;+ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {+ err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt);+ if (err < 0)+ goto cleanup; } - i_max = idx2head ? idx2head->deltas.length : 0;- j_max = wd2idx ? wd2idx->deltas.length : 0;+ if (show != GIT_STATUS_SHOW_INDEX_ONLY) {+ err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt);+ if (err < 0)+ goto cleanup;+ } - for (i = 0, j = 0; !err && (i < i_max || j < j_max); ) {- i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;- w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;+ usercb.cb = cb;+ usercb.payload = payload;+ usercb.opts = opts; - cmp = !w2i ? -1 : !i2h ? 1 : strcmp(i2h->old_file.path, w2i->old_file.path);+ if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {+ if ((err = git_diff__paired_foreach(+ head2idx, NULL, status_invoke_cb, &usercb)) < 0)+ goto cleanup; - if (cmp < 0) {- err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata);- i++;- } else if (cmp > 0) {- err = cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata);- j++;- } else {- err = cb(i2h->old_file.path, index_delta2status(i2h->status) |- workdir_delta2status(w2i->status), cbdata);- i++; j++;- }+ git_diff_list_free(head2idx);+ head2idx = NULL; } + err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb);+ cleanup: git_tree_free(head);- git_diff_list_free(idx2head);- git_diff_list_free(wd2idx);+ git_diff_list_free(head2idx);+ git_diff_list_free(idx2wd);++ if (err == GIT_EUSER)+ giterr_clear();+ return err; } int git_status_foreach( git_repository *repo,- int (*callback)(const char *, unsigned int, void *),+ git_status_cb callback, void *payload) {- git_status_options opts;+ git_status_options opts = GIT_STATUS_OPTIONS_INIT; - memset(&opts, 0, sizeof(opts)); opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED |@@ -164,22 +216,29 @@ } struct status_file_info {+ char *expected; unsigned int count; unsigned int status;- char *expected;+ int fnm_flags;+ int ambiguous; }; static int get_one_status(const char *path, unsigned int status, void *data) { struct status_file_info *sfi = data;+ int (*strcomp)(const char *a, const char *b); sfi->count++; sfi->status = status; - if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) {- giterr_set(GITERR_INVALID,- "Ambiguous path '%s' given to git_status_file", sfi->expected);- return -1;+ strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp;++ if (sfi->count > 1 ||+ (strcomp(sfi->expected, path) != 0 &&+ p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0))+ {+ sfi->ambiguous = true;+ return GIT_EAMBIGUOUS; } return 0;@@ -191,16 +250,20 @@ const char *path) { int error;- git_status_options opts;- struct status_file_info sfi;+ git_status_options opts = GIT_STATUS_OPTIONS_INIT;+ struct status_file_info sfi = {0};+ git_index *index; assert(status_flags && repo && path); - memset(&sfi, 0, sizeof(sfi));+ if ((error = git_repository_index__weakptr(&index, repo)) < 0)+ return error;+ if ((sfi.expected = git__strdup(path)) == NULL) return -1;+ if (index->ignore_case)+ sfi.fnm_flags = FNM_CASEFOLD; - memset(&opts, 0, sizeof(opts)); opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED |@@ -211,12 +274,31 @@ error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); - if (!error && !sfi.count) {+ if (error < 0 && sfi.ambiguous) { giterr_set(GITERR_INVALID,- "Attempt to get status of nonexistent file '%s'", path);- error = GIT_ENOTFOUND;+ "Ambiguous path '%s' given to git_status_file", sfi.expected);+ error = GIT_EAMBIGUOUS; } + if (!error && !sfi.count) {+ git_buf full = GIT_BUF_INIT;++ /* if the file actually exists and we still did not get a callback+ * for it, then it must be contained inside an ignored directory, so+ * mark it as such instead of generating an error.+ */+ if (!git_buf_joinpath(&full, git_repository_workdir(repo), path) &&+ git_path_exists(full.ptr))+ sfi.status = GIT_STATUS_IGNORED;+ else {+ giterr_set(GITERR_INVALID,+ "Attempt to get status of nonexistent file '%s'", path);+ error = GIT_ENOTFOUND;+ }++ git_buf_free(&full);+ }+ *status_flags = sfi.status; git__free(sfi.expected);@@ -229,14 +311,6 @@ git_repository *repo, const char *path) {- int error;- git_ignores ignores;-- if (git_ignore__for_path(repo, path, &ignores) < 0)- return -1;-- error = git_ignore__lookup(&ignores, path, ignored);- git_ignore__free(&ignores);- return error;+ return git_ignore_path_is_ignored(ignored, repo, path); }
libgit2/src/strmap.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -19,7 +19,7 @@ typedef khash_t(str) git_strmap; #define GIT__USE_STRMAP \- __KHASH_IMPL(str, static inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal)+ __KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) #define git_strmap_alloc() kh_init(str) #define git_strmap_free(h) kh_destroy(str, h), h = NULL
libgit2/src/submodule.c view
@@ -1,387 +1,1511 @@ /*- * Copyright (C) 2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#include "common.h"-#include "git2/config.h"-#include "git2/types.h"-#include "git2/repository.h"-#include "git2/index.h"-#include "git2/submodule.h"-#include "buffer.h"-#include "vector.h"-#include "posix.h"-#include "config_file.h"-#include "config.h"-#include "repository.h"--static git_cvar_map _sm_update_map[] = {- {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},- {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},- {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}-};--static git_cvar_map _sm_ignore_map[] = {- {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},- {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},- {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},- {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}-};--static inline khint_t str_hash_no_trailing_slash(const char *s)-{- khint_t h;-- for (h = 0; *s; ++s)- if (s[1] || *s != '/')- h = (h << 5) - h + *s;-- return h;-}--static inline int str_equal_no_trailing_slash(const char *a, const char *b)-{- size_t alen = a ? strlen(a) : 0;- size_t blen = b ? strlen(b) : 0;-- if (alen && a[alen] == '/')- alen--;- if (blen && b[blen] == '/')- blen--;-- return (alen == blen && strncmp(a, b, alen) == 0);-}--__KHASH_IMPL(str, static inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash);--static git_submodule *submodule_alloc(const char *name)-{- git_submodule *sm = git__calloc(1, sizeof(git_submodule));- if (sm == NULL)- return sm;-- sm->path = sm->name = git__strdup(name);- if (!sm->name) {- git__free(sm);- return NULL;- }-- return sm;-}--static void submodule_release(git_submodule *sm, int decr)-{- if (!sm)- return;-- sm->refcount -= decr;-- if (sm->refcount == 0) {- if (sm->name != sm->path)- git__free(sm->path);- git__free(sm->name);- git__free(sm->url);- git__free(sm);- }-}--static int submodule_from_entry(- git_strmap *smcfg, git_index_entry *entry)-{- git_submodule *sm;- void *old_sm;- khiter_t pos;- int error;-- pos = git_strmap_lookup_index(smcfg, entry->path);-- if (git_strmap_valid_index(smcfg, pos))- sm = git_strmap_value_at(smcfg, pos);- else- sm = submodule_alloc(entry->path);-- git_oid_cpy(&sm->oid, &entry->oid);-- if (strcmp(sm->path, entry->path) != 0) {- if (sm->path != sm->name) {- git__free(sm->path);- sm->path = sm->name;- }- sm->path = git__strdup(entry->path);- if (!sm->path)- goto fail;- }-- git_strmap_insert2(smcfg, sm->path, sm, old_sm, error);- if (error < 0)- goto fail;- sm->refcount++;-- if (old_sm && ((git_submodule *)old_sm) != sm) {- /* TODO: log warning about multiple entrys for same submodule path */- submodule_release(old_sm, 1);- }-- return 0;--fail:- submodule_release(sm, 0);- return -1;-}--static int submodule_from_config(- const char *key, const char *value, void *data)-{- git_strmap *smcfg = data;- const char *namestart;- const char *property;- git_buf name = GIT_BUF_INIT;- git_submodule *sm;- void *old_sm = NULL;- bool is_path;- khiter_t pos;- int error;-- if (git__prefixcmp(key, "submodule.") != 0)- return 0;-- namestart = key + strlen("submodule.");- property = strrchr(namestart, '.');- if (property == NULL)- return 0;- property++;- is_path = (strcmp(property, "path") == 0);-- if (git_buf_set(&name, namestart, property - namestart - 1) < 0)- return -1;-- pos = git_strmap_lookup_index(smcfg, name.ptr);- if (!git_strmap_valid_index(smcfg, pos) && is_path)- pos = git_strmap_lookup_index(smcfg, value);- if (!git_strmap_valid_index(smcfg, pos))- sm = submodule_alloc(name.ptr);- else- sm = git_strmap_value_at(smcfg, pos);- if (!sm)- goto fail;-- if (strcmp(sm->name, name.ptr) != 0) {- assert(sm->path == sm->name);- sm->name = git_buf_detach(&name);-- git_strmap_insert2(smcfg, sm->name, sm, old_sm, error);- if (error < 0)- goto fail;- sm->refcount++;- }- else if (is_path && strcmp(sm->path, value) != 0) {- assert(sm->path == sm->name);- sm->path = git__strdup(value);- if (sm->path == NULL)- goto fail;-- git_strmap_insert2(smcfg, sm->path, sm, old_sm, error);- if (error < 0)- goto fail;- sm->refcount++;- }- git_buf_free(&name);-- if (old_sm && ((git_submodule *)old_sm) != sm) {- /* TODO: log warning about multiple submodules with same path */- submodule_release(old_sm, 1);- }-- if (is_path)- return 0;-- /* copy other properties into submodule entry */- if (strcmp(property, "url") == 0) {- if (sm->url) {- git__free(sm->url);- sm->url = NULL;- }- if ((sm->url = git__strdup(value)) == NULL)- goto fail;- }- else if (strcmp(property, "update") == 0) {- int val;- if (git_config_lookup_map_value(- _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) {- giterr_set(GITERR_INVALID,- "Invalid value for submodule update property: '%s'", value);- goto fail;- }- sm->update = (git_submodule_update_t)val;- }- else if (strcmp(property, "fetchRecurseSubmodules") == 0) {- if (git__parse_bool(&sm->fetch_recurse, value) < 0) {- giterr_set(GITERR_INVALID,- "Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value);- goto fail;- }- }- else if (strcmp(property, "ignore") == 0) {- int val;- if (git_config_lookup_map_value(- _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) {- giterr_set(GITERR_INVALID,- "Invalid value for submodule ignore property: '%s'", value);- goto fail;- }- sm->ignore = (git_submodule_ignore_t)val;- }- /* ignore other unknown submodule properties */-- return 0;--fail:- submodule_release(sm, 0);- git_buf_free(&name);- return -1;-}--static int load_submodule_config(git_repository *repo)-{- int error;- git_index *index;- unsigned int i, max_i;- git_oid gitmodules_oid;- git_strmap *smcfg;- struct git_config_file *mods = NULL;-- if (repo->submodules)- return 0;-- /* submodule data is kept in a hashtable with each submodule stored- * under both its name and its path. These are usually the same, but- * that is not guaranteed.- */- smcfg = git_strmap_alloc();- GITERR_CHECK_ALLOC(smcfg);-- /* scan index for gitmodules (and .gitmodules entry) */- if ((error = git_repository_index__weakptr(&index, repo)) < 0)- goto cleanup;- memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));- max_i = git_index_entrycount(index);-- for (i = 0; i < max_i; i++) {- git_index_entry *entry = git_index_get(index, i);- if (S_ISGITLINK(entry->mode)) {- if ((error = submodule_from_entry(smcfg, entry)) < 0)- goto cleanup;- }- else if (strcmp(entry->path, ".gitmodules") == 0)- git_oid_cpy(&gitmodules_oid, &entry->oid);- }-- /* load .gitmodules from workdir if it exists */- if (git_repository_workdir(repo) != NULL) {- /* look in workdir for .gitmodules */- git_buf path = GIT_BUF_INIT;- if (!git_buf_joinpath(- &path, git_repository_workdir(repo), ".gitmodules") &&- git_path_isfile(path.ptr))- {- if (!(error = git_config_file__ondisk(&mods, path.ptr)))- error = git_config_file_open(mods);- }- git_buf_free(&path);- }-- /* load .gitmodules from object cache if not in workdir */- if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) {- /* TODO: is it worth loading gitmodules from object cache? */- }-- /* process .gitmodules info */- if (!error && mods != NULL)- error = git_config_file_foreach(mods, submodule_from_config, smcfg);-- /* store submodule config in repo */- if (!error)- repo->submodules = smcfg;--cleanup:- if (mods != NULL)- git_config_file_free(mods);- if (error)- git_strmap_free(smcfg);- return error;-}--void git_submodule_config_free(git_repository *repo)-{- git_strmap *smcfg = repo->submodules;- git_submodule *sm;-- repo->submodules = NULL;-- if (smcfg == NULL)- return;-- git_strmap_foreach_value(smcfg, sm, {- submodule_release(sm,1);- });- git_strmap_free(smcfg);-}--static int submodule_cmp(const void *a, const void *b)-{- return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);-}--int git_submodule_foreach(- git_repository *repo,- int (*callback)(const char *name, void *payload),- void *payload)-{- int error;- git_submodule *sm;- git_vector seen = GIT_VECTOR_INIT;- seen._cmp = submodule_cmp;-- if ((error = load_submodule_config(repo)) < 0)- return error;-- git_strmap_foreach_value(repo->submodules, sm, {- /* usually the following will not come into play */- if (sm->refcount > 1) {- if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND)- continue;- if ((error = git_vector_insert(&seen, sm)) < 0)- break;- }-- if ((error = callback(sm->name, payload)) < 0)- break;- });-- git_vector_free(&seen);-- return error;-}--int git_submodule_lookup(- git_submodule **sm_ptr, /* NULL allowed if user only wants to test */- git_repository *repo,- const char *name) /* trailing slash is allowed */-{- khiter_t pos;-- if (load_submodule_config(repo) < 0)- return -1;-- pos = git_strmap_lookup_index(repo->submodules, name);- if (!git_strmap_valid_index(repo->submodules, pos))- return GIT_ENOTFOUND;-- if (sm_ptr)- *sm_ptr = git_strmap_value_at(repo->submodules, pos);-- return 0;+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "common.h"+#include "git2/config.h"+#include "git2/types.h"+#include "git2/repository.h"+#include "git2/index.h"+#include "git2/submodule.h"+#include "buffer.h"+#include "buf_text.h"+#include "vector.h"+#include "posix.h"+#include "config_file.h"+#include "config.h"+#include "repository.h"+#include "submodule.h"+#include "tree.h"+#include "iterator.h"++#define GIT_MODULES_FILE ".gitmodules"++static git_cvar_map _sm_update_map[] = {+ {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT},+ {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE},+ {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE},+ {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE},+};++static git_cvar_map _sm_ignore_map[] = {+ {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE},+ {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED},+ {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY},+ {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL},+};++static kh_inline khint_t str_hash_no_trailing_slash(const char *s)+{+ khint_t h;++ for (h = 0; *s; ++s)+ if (s[1] != '\0' || *s != '/')+ h = (h << 5) - h + *s;++ return h;+}++static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b)+{+ size_t alen = a ? strlen(a) : 0;+ size_t blen = b ? strlen(b) : 0;++ if (alen > 0 && a[alen - 1] == '/')+ alen--;+ if (blen > 0 && b[blen - 1] == '/')+ blen--;++ return (alen == blen && strncmp(a, b, alen) == 0);+}++__KHASH_IMPL(+ str, static kh_inline, const char *, void *, 1,+ str_hash_no_trailing_slash, str_equal_no_trailing_slash);++static int load_submodule_config(git_repository *repo);+static git_config_backend *open_gitmodules(git_repository *, bool, const git_oid *);+static int lookup_head_remote(git_buf *url, git_repository *repo);+static int submodule_get(git_submodule **, git_repository *, const char *, const char *);+static void submodule_release(git_submodule *sm, int decr);+static int submodule_load_from_index(git_repository *, const git_index_entry *);+static int submodule_load_from_head(git_repository*, const char*, const git_oid*);+static int submodule_load_from_config(const git_config_entry *, void *);+static int submodule_load_from_wd_lite(git_submodule *, const char *, void *);+static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool);+static void submodule_mode_mismatch(git_repository *, const char *, unsigned int);+static int submodule_index_status(unsigned int *status, git_submodule *sm);+static int submodule_wd_status(unsigned int *status, git_submodule *sm);++static int submodule_cmp(const void *a, const void *b)+{+ return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name);+}++static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix)+{+ ssize_t idx = git_buf_rfind(key, '.');+ git_buf_truncate(key, (size_t)(idx + 1));+ return git_buf_puts(key, suffix);+}++/*+ * PUBLIC APIS+ */++int git_submodule_lookup(+ git_submodule **sm_ptr, /* NULL if user only wants to test existence */+ git_repository *repo,+ const char *name) /* trailing slash is allowed */+{+ int error;+ khiter_t pos;++ assert(repo && name);++ if ((error = load_submodule_config(repo)) < 0)+ return error;++ pos = git_strmap_lookup_index(repo->submodules, name);++ if (!git_strmap_valid_index(repo->submodules, pos)) {+ error = GIT_ENOTFOUND;++ /* check if a plausible submodule exists at path */+ if (git_repository_workdir(repo)) {+ git_buf path = GIT_BUF_INIT;++ if (git_buf_joinpath(&path, git_repository_workdir(repo), name) < 0)+ return -1;++ if (git_path_contains_dir(&path, DOT_GIT))+ error = GIT_EEXISTS;++ git_buf_free(&path);+ }++ return error;+ }++ if (sm_ptr)+ *sm_ptr = git_strmap_value_at(repo->submodules, pos);++ return 0;+}++int git_submodule_foreach(+ git_repository *repo,+ int (*callback)(git_submodule *sm, const char *name, void *payload),+ void *payload)+{+ int error;+ git_submodule *sm;+ git_vector seen = GIT_VECTOR_INIT;+ seen._cmp = submodule_cmp;++ assert(repo && callback);++ if ((error = load_submodule_config(repo)) < 0)+ return error;++ git_strmap_foreach_value(repo->submodules, sm, {+ /* Usually the following will not come into play - it just prevents+ * us from issuing a callback twice for a submodule where the name+ * and path are not the same.+ */+ if (sm->refcount > 1) {+ if (git_vector_bsearch(NULL, &seen, sm) != GIT_ENOTFOUND)+ continue;+ if ((error = git_vector_insert(&seen, sm)) < 0)+ break;+ }++ if (callback(sm, sm->name, payload)) {+ giterr_clear();+ error = GIT_EUSER;+ break;+ }+ });++ git_vector_free(&seen);++ return error;+}++void git_submodule_config_free(git_repository *repo)+{+ git_strmap *smcfg;+ git_submodule *sm;++ assert(repo);++ smcfg = repo->submodules;+ repo->submodules = NULL;++ if (smcfg == NULL)+ return;++ git_strmap_foreach_value(smcfg, sm, {+ submodule_release(sm,1);+ });+ git_strmap_free(smcfg);+}++int git_submodule_add_setup(+ git_submodule **submodule,+ git_repository *repo,+ const char *url,+ const char *path,+ int use_gitlink)+{+ int error = 0;+ git_config_backend *mods = NULL;+ git_submodule *sm;+ git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT;+ git_repository_init_options initopt = GIT_REPOSITORY_INIT_OPTIONS_INIT;+ git_repository *subrepo = NULL;++ assert(repo && url && path);++ /* see if there is already an entry for this submodule */++ if (git_submodule_lookup(&sm, repo, path) < 0)+ giterr_clear();+ else {+ giterr_set(GITERR_SUBMODULE,+ "Attempt to add a submodule that already exists");+ return GIT_EEXISTS;+ }++ /* resolve parameters */++ if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) {+ if (!(error = lookup_head_remote(&real_url, repo)))+ error = git_path_apply_relative(&real_url, url);+ } else if (strchr(url, ':') != NULL || url[0] == '/') {+ error = git_buf_sets(&real_url, url);+ } else {+ giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL");+ error = -1;+ }+ if (error)+ goto cleanup;++ /* validate and normalize path */++ if (git__prefixcmp(path, git_repository_workdir(repo)) == 0)+ path += strlen(git_repository_workdir(repo));++ if (git_path_root(path) >= 0) {+ giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path");+ error = -1;+ goto cleanup;+ }++ /* update .gitmodules */++ if ((mods = open_gitmodules(repo, true, NULL)) == NULL) {+ giterr_set(GITERR_SUBMODULE,+ "Adding submodules to a bare repository is not supported (for now)");+ return -1;+ }++ if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 ||+ (error = git_config_file_set_string(mods, name.ptr, path)) < 0)+ goto cleanup;++ if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 ||+ (error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0)+ goto cleanup;++ git_buf_clear(&name);++ /* init submodule repository and add origin remote as needed */++ error = git_buf_joinpath(&name, git_repository_workdir(repo), path);+ if (error < 0)+ goto cleanup;++ /* New style: sub-repo goes in <repo-dir>/modules/<name>/ with a+ * gitlink in the sub-repo workdir directory to that repository+ *+ * Old style: sub-repo goes directly into repo/<name>/.git/+ */++ initopt.flags = GIT_REPOSITORY_INIT_MKPATH |+ GIT_REPOSITORY_INIT_NO_REINIT;+ initopt.origin_url = real_url.ptr;++ if (git_path_exists(name.ptr) &&+ git_path_contains(&name, DOT_GIT))+ {+ /* repo appears to already exist - reinit? */+ }+ else if (use_gitlink) {+ git_buf repodir = GIT_BUF_INIT;++ error = git_buf_join_n(+ &repodir, '/', 3, git_repository_path(repo), "modules", path);+ if (error < 0)+ goto cleanup;++ initopt.workdir_path = name.ptr;+ initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;++ error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt);++ git_buf_free(&repodir);+ }+ else {+ error = git_repository_init_ext(&subrepo, name.ptr, &initopt);+ }+ if (error < 0)+ goto cleanup;++ /* add submodule to hash and "reload" it */++ if (!(error = submodule_get(&sm, repo, path, NULL)) &&+ !(error = git_submodule_reload(sm)))+ error = git_submodule_init(sm, false);++cleanup:+ if (submodule != NULL)+ *submodule = !error ? sm : NULL;++ if (mods != NULL)+ git_config_file_free(mods);+ git_repository_free(subrepo);+ git_buf_free(&real_url);+ git_buf_free(&name);++ return error;+}++int git_submodule_add_finalize(git_submodule *sm)+{+ int error;+ git_index *index;++ assert(sm);++ if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 ||+ (error = git_index_add_bypath(index, GIT_MODULES_FILE)) < 0)+ return error;++ return git_submodule_add_to_index(sm, true);+}++int git_submodule_add_to_index(git_submodule *sm, int write_index)+{+ int error;+ git_repository *repo, *sm_repo = NULL;+ git_index *index;+ git_buf path = GIT_BUF_INIT;+ git_commit *head;+ git_index_entry entry;+ struct stat st;++ assert(sm);++ repo = sm->owner;++ /* force reload of wd OID by git_submodule_open */+ sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID;++ if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||+ (error = git_buf_joinpath(+ &path, git_repository_workdir(repo), sm->path)) < 0 ||+ (error = git_submodule_open(&sm_repo, sm)) < 0)+ goto cleanup;++ /* read stat information for submodule working directory */+ if (p_stat(path.ptr, &st) < 0) {+ giterr_set(GITERR_SUBMODULE,+ "Cannot add submodule without working directory");+ error = -1;+ goto cleanup;+ }++ memset(&entry, 0, sizeof(entry));+ entry.path = sm->path;+ git_index_entry__init_from_stat(&entry, &st);++ /* calling git_submodule_open will have set sm->wd_oid if possible */+ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {+ giterr_set(GITERR_SUBMODULE,+ "Cannot add submodule without HEAD to index");+ error = -1;+ goto cleanup;+ }+ git_oid_cpy(&entry.oid, &sm->wd_oid);++ if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0)+ goto cleanup;++ entry.ctime.seconds = git_commit_time(head);+ entry.ctime.nanoseconds = 0;+ entry.mtime.seconds = git_commit_time(head);+ entry.mtime.nanoseconds = 0;++ git_commit_free(head);++ /* add it */+ error = git_index_add(index, &entry);++ /* write it, if requested */+ if (!error && write_index) {+ error = git_index_write(index);++ if (!error)+ git_oid_cpy(&sm->index_oid, &sm->wd_oid);+ }++cleanup:+ git_repository_free(sm_repo);+ git_buf_free(&path);+ return error;+}++int git_submodule_save(git_submodule *submodule)+{+ int error = 0;+ git_config_backend *mods;+ git_buf key = GIT_BUF_INIT;++ assert(submodule);++ mods = open_gitmodules(submodule->owner, true, NULL);+ if (!mods) {+ giterr_set(GITERR_SUBMODULE,+ "Adding submodules to a bare repository is not supported (for now)");+ return -1;+ }++ if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0)+ goto cleanup;++ /* save values for path, url, update, ignore, fetchRecurseSubmodules */++ if ((error = submodule_config_key_trunc_puts(&key, "path")) < 0 ||+ (error = git_config_file_set_string(mods, key.ptr, submodule->path)) < 0)+ goto cleanup;++ if ((error = submodule_config_key_trunc_puts(&key, "url")) < 0 ||+ (error = git_config_file_set_string(mods, key.ptr, submodule->url)) < 0)+ goto cleanup;++ if (!(error = submodule_config_key_trunc_puts(&key, "update")) &&+ submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)+ {+ const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ?+ NULL : _sm_update_map[submodule->update].str_match;+ error = git_config_file_set_string(mods, key.ptr, val);+ }+ if (error < 0)+ goto cleanup;++ if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) &&+ submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT)+ {+ const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ?+ NULL : _sm_ignore_map[submodule->ignore].str_match;+ error = git_config_file_set_string(mods, key.ptr, val);+ }+ if (error < 0)+ goto cleanup;++ if ((error = submodule_config_key_trunc_puts(+ &key, "fetchRecurseSubmodules")) < 0 ||+ (error = git_config_file_set_string(+ mods, key.ptr, submodule->fetch_recurse ? "true" : "false")) < 0)+ goto cleanup;++ /* update internal defaults */++ submodule->ignore_default = submodule->ignore;+ submodule->update_default = submodule->update;+ submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;++cleanup:+ if (mods != NULL)+ git_config_file_free(mods);+ git_buf_free(&key);++ return error;+}++git_repository *git_submodule_owner(git_submodule *submodule)+{+ assert(submodule);+ return submodule->owner;+}++const char *git_submodule_name(git_submodule *submodule)+{+ assert(submodule);+ return submodule->name;+}++const char *git_submodule_path(git_submodule *submodule)+{+ assert(submodule);+ return submodule->path;+}++const char *git_submodule_url(git_submodule *submodule)+{+ assert(submodule);+ return submodule->url;+}++int git_submodule_set_url(git_submodule *submodule, const char *url)+{+ assert(submodule && url);++ git__free(submodule->url);++ submodule->url = git__strdup(url);+ GITERR_CHECK_ALLOC(submodule->url);++ return 0;+}++const git_oid *git_submodule_index_id(git_submodule *submodule)+{+ assert(submodule);++ if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID)+ return &submodule->index_oid;+ else+ return NULL;+}++const git_oid *git_submodule_head_id(git_submodule *submodule)+{+ assert(submodule);++ if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID)+ return &submodule->head_oid;+ else+ return NULL;+}++const git_oid *git_submodule_wd_id(git_submodule *submodule)+{+ assert(submodule);++ if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) {+ git_repository *subrepo;++ /* calling submodule open grabs the HEAD OID if possible */+ if (!git_submodule_open(&subrepo, submodule))+ git_repository_free(subrepo);+ else+ giterr_clear();+ }++ if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)+ return &submodule->wd_oid;+ else+ return NULL;+}++git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule)+{+ assert(submodule);+ return submodule->ignore;+}++git_submodule_ignore_t git_submodule_set_ignore(+ git_submodule *submodule, git_submodule_ignore_t ignore)+{+ git_submodule_ignore_t old;++ assert(submodule);++ if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT)+ ignore = submodule->ignore_default;++ old = submodule->ignore;+ submodule->ignore = ignore;+ return old;+}++git_submodule_update_t git_submodule_update(git_submodule *submodule)+{+ assert(submodule);+ return submodule->update;+}++git_submodule_update_t git_submodule_set_update(+ git_submodule *submodule, git_submodule_update_t update)+{+ git_submodule_update_t old;++ assert(submodule);++ if (update == GIT_SUBMODULE_UPDATE_DEFAULT)+ update = submodule->update_default;++ old = submodule->update;+ submodule->update = update;+ return old;+}++int git_submodule_fetch_recurse_submodules(+ git_submodule *submodule)+{+ assert(submodule);+ return submodule->fetch_recurse;+}++int git_submodule_set_fetch_recurse_submodules(+ git_submodule *submodule,+ int fetch_recurse_submodules)+{+ int old;++ assert(submodule);++ old = submodule->fetch_recurse;+ submodule->fetch_recurse = (fetch_recurse_submodules != 0);+ return old;+}++int git_submodule_init(git_submodule *submodule, int overwrite)+{+ int error;++ /* write "submodule.NAME.url" */++ if (!submodule->url) {+ giterr_set(GITERR_SUBMODULE,+ "No URL configured for submodule '%s'", submodule->name);+ return -1;+ }++ error = submodule_update_config(+ submodule, "url", submodule->url, overwrite != 0, false);+ if (error < 0)+ return error;++ /* write "submodule.NAME.update" if not default */++ if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT)+ error = submodule_update_config(+ submodule, "update", NULL, (overwrite != 0), false);+ else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT)+ error = submodule_update_config(+ submodule, "update",+ _sm_update_map[submodule->update].str_match,+ (overwrite != 0), false);++ return error;+}++int git_submodule_sync(git_submodule *submodule)+{+ if (!submodule->url) {+ giterr_set(GITERR_SUBMODULE,+ "No URL configured for submodule '%s'", submodule->name);+ return -1;+ }++ /* copy URL over to config only if it already exists */++ return submodule_update_config(+ submodule, "url", submodule->url, true, true);+}++int git_submodule_open(+ git_repository **subrepo,+ git_submodule *submodule)+{+ int error;+ git_buf path = GIT_BUF_INIT;+ git_repository *repo;+ const char *workdir;++ assert(submodule && subrepo);++ repo = submodule->owner;+ workdir = git_repository_workdir(repo);++ if (!workdir) {+ giterr_set(GITERR_REPOSITORY,+ "Cannot open submodule repository in a bare repo");+ return GIT_ENOTFOUND;+ }++ if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) {+ giterr_set(GITERR_REPOSITORY,+ "Cannot open submodule repository that is not checked out");+ return GIT_ENOTFOUND;+ }++ if (git_buf_joinpath(&path, workdir, submodule->path) < 0)+ return -1;++ error = git_repository_open(subrepo, path.ptr);++ git_buf_free(&path);++ /* if we have opened the submodule successfully, let's grab the HEAD OID */+ if (!error) {+ if (!git_reference_name_to_id(+ &submodule->wd_oid, *subrepo, GIT_HEAD_FILE))+ submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID;+ else+ giterr_clear();+ }++ return error;+}++int git_submodule_reload_all(git_repository *repo)+{+ assert(repo);+ git_submodule_config_free(repo);+ return load_submodule_config(repo);+}++int git_submodule_reload(git_submodule *submodule)+{+ git_repository *repo;+ git_index *index;+ int error;+ size_t pos;+ git_tree *head;+ git_config_backend *mods;++ assert(submodule);++ /* refresh index data */++ repo = submodule->owner;+ if (git_repository_index__weakptr(&index, repo) < 0)+ return -1;++ submodule->flags = submodule->flags &+ ~(GIT_SUBMODULE_STATUS_IN_INDEX |+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID);++ if (!git_index_find(&pos, index, submodule->path)) {+ const git_index_entry *entry = git_index_get_byindex(index, pos);++ if (S_ISGITLINK(entry->mode)) {+ if ((error = submodule_load_from_index(repo, entry)) < 0)+ return error;+ } else {+ submodule_mode_mismatch(+ repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);+ }+ }++ /* refresh HEAD tree data */++ if (!(error = git_repository_head_tree(&head, repo))) {+ git_tree_entry *te;++ submodule->flags = submodule->flags &+ ~(GIT_SUBMODULE_STATUS_IN_HEAD |+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID);++ if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) {++ if (S_ISGITLINK(te->attr)) {+ error = submodule_load_from_head(repo, submodule->path, &te->oid);+ } else {+ submodule_mode_mismatch(+ repo, submodule->path,+ GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);+ }++ git_tree_entry_free(te);+ }+ else if (error == GIT_ENOTFOUND) {+ giterr_clear();+ error = 0;+ }++ git_tree_free(head);+ }++ if (error < 0)+ return error;++ /* refresh config data */++ if ((mods = open_gitmodules(repo, false, NULL)) != NULL) {+ git_buf path = GIT_BUF_INIT;++ git_buf_sets(&path, "submodule\\.");+ git_buf_text_puts_escape_regex(&path, submodule->name);+ git_buf_puts(&path, ".*");++ if (git_buf_oom(&path))+ error = -1;+ else+ error = git_config_file_foreach_match(+ mods, path.ptr, submodule_load_from_config, repo);++ git_buf_free(&path);+ git_config_file_free(mods);+ }++ if (error < 0)+ return error;++ /* refresh wd data */++ submodule->flags = submodule->flags &+ ~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID);++ error = submodule_load_from_wd_lite(submodule, submodule->path, NULL);++ return error;+}++int git_submodule_status(+ unsigned int *status,+ git_submodule *submodule)+{+ int error = 0;+ unsigned int status_val;++ assert(status && submodule);++ status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags);++ if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) {+ if (!(error = submodule_index_status(&status_val, submodule)))+ error = submodule_wd_status(&status_val, submodule);+ }++ *status = status_val;++ return error;+}++int git_submodule_location(+ unsigned int *location_status,+ git_submodule *submodule)+{+ assert(location_status && submodule);++ *location_status = submodule->flags &+ (GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX |+ GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD);++ return 0;+}+++/*+ * INTERNAL FUNCTIONS+ */++static git_submodule *submodule_alloc(git_repository *repo, const char *name)+{+ git_submodule *sm;++ if (!name || !strlen(name)) {+ giterr_set(GITERR_SUBMODULE, "Invalid submodule name");+ return NULL;+ }++ sm = git__calloc(1, sizeof(git_submodule));+ if (sm == NULL)+ goto fail;++ sm->path = sm->name = git__strdup(name);+ if (!sm->name)+ goto fail;++ sm->owner = repo;+ sm->refcount = 1;++ return sm;++fail:+ submodule_release(sm, 0);+ return NULL;+}++static void submodule_release(git_submodule *sm, int decr)+{+ if (!sm)+ return;++ sm->refcount -= decr;++ if (sm->refcount == 0) {+ if (sm->name != sm->path) {+ git__free(sm->path);+ sm->path = NULL;+ }++ git__free(sm->name);+ sm->name = NULL;++ git__free(sm->url);+ sm->url = NULL;++ sm->owner = NULL;++ git__free(sm);+ }+}++static int submodule_get(+ git_submodule **sm_ptr,+ git_repository *repo,+ const char *name,+ const char *alternate)+{+ git_strmap *smcfg = repo->submodules;+ khiter_t pos;+ git_submodule *sm;+ int error;++ assert(repo && name);++ pos = git_strmap_lookup_index(smcfg, name);++ if (!git_strmap_valid_index(smcfg, pos) && alternate)+ pos = git_strmap_lookup_index(smcfg, alternate);++ if (!git_strmap_valid_index(smcfg, pos)) {+ sm = submodule_alloc(repo, name);++ /* insert value at name - if another thread beats us to it, then use+ * their record and release our own.+ */+ pos = kh_put(str, smcfg, sm->name, &error);++ if (error < 0) {+ submodule_release(sm, 1);+ sm = NULL;+ } else if (error == 0) {+ submodule_release(sm, 1);+ sm = git_strmap_value_at(smcfg, pos);+ } else {+ git_strmap_set_value_at(smcfg, pos, sm);+ }+ } else {+ sm = git_strmap_value_at(smcfg, pos);+ }++ *sm_ptr = sm;++ return (sm != NULL) ? 0 : -1;+}++static int submodule_load_from_index(+ git_repository *repo, const git_index_entry *entry)+{+ git_submodule *sm;++ if (submodule_get(&sm, repo, entry->path, NULL) < 0)+ return -1;++ if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) {+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES;+ return 0;+ }++ sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX;++ git_oid_cpy(&sm->index_oid, &entry->oid);+ sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID;++ return 0;+}++static int submodule_load_from_head(+ git_repository *repo, const char *path, const git_oid *oid)+{+ git_submodule *sm;++ if (submodule_get(&sm, repo, path, NULL) < 0)+ return -1;++ sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD;++ git_oid_cpy(&sm->head_oid, oid);+ sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID;++ return 0;+}++static int submodule_config_error(const char *property, const char *value)+{+ giterr_set(GITERR_INVALID,+ "Invalid value for submodule '%s' property: '%s'", property, value);+ return -1;+}++static int submodule_load_from_config(+ const git_config_entry *entry, void *data)+{+ git_repository *repo = data;+ git_strmap *smcfg = repo->submodules;+ const char *namestart, *property, *alternate = NULL;+ const char *key = entry->name, *value = entry->value;+ git_buf name = GIT_BUF_INIT;+ git_submodule *sm;+ bool is_path;+ int error = 0;++ if (git__prefixcmp(key, "submodule.") != 0)+ return 0;++ namestart = key + strlen("submodule.");+ property = strrchr(namestart, '.');+ if (property == NULL)+ return 0;+ property++;+ is_path = (strcasecmp(property, "path") == 0);++ if (git_buf_set(&name, namestart, property - namestart - 1) < 0)+ return -1;++ if (submodule_get(&sm, repo, name.ptr, is_path ? value : NULL) < 0) {+ git_buf_free(&name);+ return -1;+ }++ sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG;++ /* Only from config might we get differing names & paths. If so, then+ * update the submodule and insert under the alternative key.+ */++ /* TODO: if case insensitive filesystem, then the following strcmps+ * should be strcasecmp+ */++ if (strcmp(sm->name, name.ptr) != 0) {+ alternate = sm->name = git_buf_detach(&name);+ } else if (is_path && value && strcmp(sm->path, value) != 0) {+ alternate = sm->path = git__strdup(value);+ if (!sm->path)+ error = -1;+ }+ if (alternate) {+ void *old_sm = NULL;+ git_strmap_insert2(smcfg, alternate, sm, old_sm, error);++ if (error >= 0)+ sm->refcount++; /* inserted under a new key */++ /* if we replaced an old module under this key, release the old one */+ if (old_sm && ((git_submodule *)old_sm) != sm) {+ submodule_release(old_sm, 1);+ /* TODO: log warning about multiple submodules with same path */+ }+ }++ git_buf_free(&name);+ if (error < 0)+ return error;++ /* TODO: Look up path in index and if it is present but not a GITLINK+ * then this should be deleted (at least to match git's behavior)+ */++ if (is_path)+ return 0;++ /* copy other properties into submodule entry */+ if (strcasecmp(property, "url") == 0) {+ git__free(sm->url);+ sm->url = NULL;++ if (value != NULL && (sm->url = git__strdup(value)) == NULL)+ return -1;+ }+ else if (strcasecmp(property, "update") == 0) {+ int val;+ if (git_config_lookup_map_value(+ &val, _sm_update_map, ARRAY_SIZE(_sm_update_map), value) < 0)+ return submodule_config_error("update", value);+ sm->update_default = sm->update = (git_submodule_update_t)val;+ }+ else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) {+ if (git__parse_bool(&sm->fetch_recurse, value) < 0)+ return submodule_config_error("fetchRecurseSubmodules", value);+ }+ else if (strcasecmp(property, "ignore") == 0) {+ int val;+ if (git_config_lookup_map_value(+ &val, _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value) < 0)+ return submodule_config_error("ignore", value);+ sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val;+ }+ /* ignore other unknown submodule properties */++ return 0;+}++static int submodule_load_from_wd_lite(+ git_submodule *sm, const char *name, void *payload)+{+ git_repository *repo = git_submodule_owner(sm);+ git_buf path = GIT_BUF_INIT;++ GIT_UNUSED(name);+ GIT_UNUSED(payload);++ if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0)+ return -1;++ if (git_path_isdir(path.ptr))+ sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED;++ if (git_path_contains(&path, DOT_GIT))+ sm->flags |= GIT_SUBMODULE_STATUS_IN_WD;++ git_buf_free(&path);++ return 0;+}++static void submodule_mode_mismatch(+ git_repository *repo, const char *path, unsigned int flag)+{+ khiter_t pos = git_strmap_lookup_index(repo->submodules, path);++ if (git_strmap_valid_index(repo->submodules, pos)) {+ git_submodule *sm = git_strmap_value_at(repo->submodules, pos);++ sm->flags |= flag;+ }+}++static int load_submodule_config_from_index(+ git_repository *repo, git_oid *gitmodules_oid)+{+ int error;+ git_index *index;+ git_iterator *i;+ const git_index_entry *entry;++ if ((error = git_repository_index__weakptr(&index, repo)) < 0 ||+ (error = git_iterator_for_index(&i, index, 0, NULL, NULL)) < 0)+ return error;++ error = git_iterator_current(&entry, i);++ while (!error && entry != NULL) {++ if (S_ISGITLINK(entry->mode)) {+ error = submodule_load_from_index(repo, entry);+ if (error < 0)+ break;+ } else {+ submodule_mode_mismatch(+ repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE);++ if (strcmp(entry->path, GIT_MODULES_FILE) == 0)+ git_oid_cpy(gitmodules_oid, &entry->oid);+ }++ error = git_iterator_advance(&entry, i);+ }++ git_iterator_free(i);++ return error;+}++static int load_submodule_config_from_head(+ git_repository *repo, git_oid *gitmodules_oid)+{+ int error;+ git_tree *head;+ git_iterator *i;+ const git_index_entry *entry;++ if ((error = git_repository_head_tree(&head, repo)) < 0)+ return error;++ if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) {+ git_tree_free(head);+ return error;+ }++ error = git_iterator_current(&entry, i);++ while (!error && entry != NULL) {++ if (S_ISGITLINK(entry->mode)) {+ error = submodule_load_from_head(repo, entry->path, &entry->oid);+ if (error < 0)+ break;+ } else {+ submodule_mode_mismatch(+ repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE);++ if (strcmp(entry->path, GIT_MODULES_FILE) == 0 &&+ git_oid_iszero(gitmodules_oid))+ git_oid_cpy(gitmodules_oid, &entry->oid);+ }++ error = git_iterator_advance(&entry, i);+ }++ git_iterator_free(i);+ git_tree_free(head);++ return error;+}++static git_config_backend *open_gitmodules(+ git_repository *repo,+ bool okay_to_create,+ const git_oid *gitmodules_oid)+{+ const char *workdir = git_repository_workdir(repo);+ git_buf path = GIT_BUF_INIT;+ git_config_backend *mods = NULL;++ if (workdir != NULL) {+ if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0)+ return NULL;++ if (okay_to_create || git_path_isfile(path.ptr)) {+ /* git_config_file__ondisk should only fail if OOM */+ if (git_config_file__ondisk(&mods, path.ptr) < 0)+ mods = NULL;+ /* open should only fail here if the file is malformed */+ else if (git_config_file_open(mods, GIT_CONFIG_LEVEL_LOCAL) < 0) {+ git_config_file_free(mods);+ mods = NULL;+ }+ }+ }++ if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) {+ /* TODO: Retrieve .gitmodules content from ODB */++ /* Should we actually do this? Core git does not, but it means you+ * can't really get much information about submodules on bare repos.+ */+ }++ git_buf_free(&path);++ return mods;+}++static int load_submodule_config(git_repository *repo)+{+ int error;+ git_oid gitmodules_oid;+ git_buf path = GIT_BUF_INIT;+ git_config_backend *mods = NULL;++ if (repo->submodules)+ return 0;++ memset(&gitmodules_oid, 0, sizeof(gitmodules_oid));++ /* Submodule data is kept in a hashtable keyed by both name and path.+ * These are usually the same, but that is not guaranteed.+ */+ if (!repo->submodules) {+ repo->submodules = git_strmap_alloc();+ GITERR_CHECK_ALLOC(repo->submodules);+ }++ /* add submodule information from index */++ if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0)+ goto cleanup;++ /* add submodule information from HEAD */++ if ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0)+ goto cleanup;++ /* add submodule information from .gitmodules */++ if ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL)+ error = git_config_file_foreach(mods, submodule_load_from_config, repo);++ if (error != 0)+ goto cleanup;++ /* shallow scan submodules in work tree */++ if (!git_repository_is_bare(repo))+ error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL);++cleanup:+ git_buf_free(&path);++ if (mods != NULL)+ git_config_file_free(mods);++ if (error)+ git_submodule_config_free(repo);++ return error;+}++static int lookup_head_remote(git_buf *url, git_repository *repo)+{+ int error;+ git_config *cfg;+ git_reference *head = NULL, *remote = NULL;+ const char *tgt, *scan;+ git_buf key = GIT_BUF_INIT;++ /* 1. resolve HEAD -> refs/heads/BRANCH+ * 2. lookup config branch.BRANCH.remote -> ORIGIN+ * 3. lookup remote.ORIGIN.url+ */++ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)+ return error;++ if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) {+ giterr_set(GITERR_SUBMODULE,+ "Cannot resolve relative URL when HEAD cannot be resolved");+ error = GIT_ENOTFOUND;+ goto cleanup;+ }++ if (git_reference_type(head) != GIT_REF_SYMBOLIC) {+ giterr_set(GITERR_SUBMODULE,+ "Cannot resolve relative URL when HEAD is not symbolic");+ error = GIT_ENOTFOUND;+ goto cleanup;+ }++ if ((error = git_branch_upstream(&remote, head)) < 0)+ goto cleanup;++ /* remote should refer to something like refs/remotes/ORIGIN/BRANCH */++ if (git_reference_type(remote) != GIT_REF_SYMBOLIC ||+ git__prefixcmp(git_reference_symbolic_target(remote), GIT_REFS_REMOTES_DIR) != 0)+ {+ giterr_set(GITERR_SUBMODULE,+ "Cannot resolve relative URL when HEAD is not symbolic");+ error = GIT_ENOTFOUND;+ goto cleanup;+ }++ scan = tgt = git_reference_symbolic_target(remote) + strlen(GIT_REFS_REMOTES_DIR);+ while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\')))+ scan++; /* find non-escaped slash to end ORIGIN name */++ error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt);+ if (error < 0)+ goto cleanup;++ if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0)+ goto cleanup;++ error = git_buf_sets(url, tgt);++cleanup:+ git_buf_free(&key);+ git_reference_free(head);+ git_reference_free(remote);++ return error;+}++static int submodule_update_config(+ git_submodule *submodule,+ const char *attr,+ const char *value,+ bool overwrite,+ bool only_existing)+{+ int error;+ git_config *config;+ git_buf key = GIT_BUF_INIT;+ const char *old = NULL;++ assert(submodule);++ error = git_repository_config__weakptr(&config, submodule->owner);+ if (error < 0)+ return error;++ error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr);+ if (error < 0)+ goto cleanup;++ if (git_config_get_string(&old, config, key.ptr) < 0)+ giterr_clear();++ if (!old && only_existing)+ goto cleanup;+ if (old && !overwrite)+ goto cleanup;+ if ((!old && !value) || (old && value && strcmp(old, value) == 0))+ goto cleanup;++ if (!value)+ error = git_config_delete_entry(config, key.ptr);+ else+ error = git_config_set_string(config, key.ptr, value);++cleanup:+ git_buf_free(&key);+ return error;+}++static int submodule_index_status(unsigned int *status, git_submodule *sm)+{+ const git_oid *head_oid = git_submodule_head_id(sm);+ const git_oid *index_oid = git_submodule_index_id(sm);++ if (!head_oid) {+ if (index_oid)+ *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED;+ }+ else if (!index_oid)+ *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED;+ else if (!git_oid_equal(head_oid, index_oid))+ *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED;++ return 0;+}++static int submodule_wd_status(unsigned int *status, git_submodule *sm)+{+ int error = 0;+ const git_oid *wd_oid, *index_oid;+ git_repository *sm_repo = NULL;++ /* open repo now if we need it (so wd_id() call won't reopen) */+ if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE ||+ sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) &&+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0)+ {+ if ((error = git_submodule_open(&sm_repo, sm)) < 0)+ return error;+ }++ index_oid = git_submodule_index_id(sm);+ wd_oid = git_submodule_wd_id(sm);++ if (!index_oid) {+ if (wd_oid)+ *status |= GIT_SUBMODULE_STATUS_WD_ADDED;+ }+ else if (!wd_oid) {+ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 &&+ (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0)+ *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED;+ else+ *status |= GIT_SUBMODULE_STATUS_WD_DELETED;+ }+ else if (!git_oid_equal(index_oid, wd_oid))+ *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED;++ if (sm_repo != NULL) {+ git_tree *sm_head;+ git_diff_options opt = GIT_DIFF_OPTIONS_INIT;+ git_diff_list *diff;++ /* the diffs below could be optimized with an early termination+ * option to the git_diff functions, but for now this is sufficient+ * (and certainly no worse that what core git does).+ */++ /* perform head-to-index diff on submodule */++ if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0)+ return error;++ if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE)+ opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;++ error = git_diff_tree_to_index(&diff, sm_repo, sm_head, NULL, &opt);++ if (!error) {+ if (git_diff_num_deltas(diff) > 0)+ *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;++ git_diff_list_free(diff);+ diff = NULL;+ }++ git_tree_free(sm_head);++ if (error < 0)+ return error;++ /* perform index-to-workdir diff on submodule */++ error = git_diff_index_to_workdir(&diff, sm_repo, NULL, &opt);++ if (!error) {+ size_t untracked =+ git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);++ if (untracked > 0)+ *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;++ if (git_diff_num_deltas(diff) != untracked)+ *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;++ git_diff_list_free(diff);+ diff = NULL;+ }++ git_repository_free(sm_repo);+ }++ return error; }
+ libgit2/src/submodule.h view
@@ -0,0 +1,102 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_submodule_h__+#define INCLUDE_submodule_h__++/* Notes:+ *+ * Submodule information can be in four places: the index, the config files+ * (both .git/config and .gitmodules), the HEAD tree, and the working+ * directory.+ *+ * In the index:+ * - submodule is found by path+ * - may be missing, present, or of the wrong type+ * - will have an oid if present+ *+ * In the HEAD tree:+ * - submodule is found by path+ * - may be missing, present, or of the wrong type+ * - will have an oid if present+ *+ * In the config files:+ * - submodule is found by submodule "name" which is usually the path+ * - may be missing or present+ * - will have a name, path, url, and other properties+ *+ * In the working directory:+ * - submodule is found by path+ * - may be missing, an empty directory, a checked out directory,+ * or of the wrong type+ * - if checked out, will have a HEAD oid+ * - if checked out, will have git history that can be used to compare oids+ * - if checked out, may have modified files and/or untracked files+ */++/**+ * Description of submodule+ *+ * This record describes a submodule found in a repository. There should be+ * an entry for every submodule found in the HEAD and index, and for every+ * submodule described in .gitmodules. The fields are as follows:+ *+ * - `owner` is the git_repository containing this submodule+ * - `name` is the name of the submodule from .gitmodules.+ * - `path` is the path to the submodule from the repo root. It is almost+ * always the same as `name`.+ * - `url` is the url for the submodule.+ * - `tree_oid` is the SHA1 for the submodule path in the repo HEAD.+ * - `index_oid` is the SHA1 for the submodule recorded in the index.+ * - `workdir_oid` is the SHA1 for the HEAD of the checked out submodule.+ * - `update` is a git_submodule_update_t value - see gitmodules(5) update.+ * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore.+ * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules.+ * - `refcount` tracks how many hashmap entries there are for this submodule.+ * It only comes into play if the name and path of the submodule differ.+ * - `flags` is for internal use, tracking where this submodule has been+ * found (head, index, config, workdir) and other misc info about it.+ *+ * If the submodule has been added to .gitmodules but not yet git added,+ * then the `index_oid` will be valid and zero. If the submodule has been+ * deleted, but the delete has not been committed yet, then the `index_oid`+ * will be set, but the `url` will be NULL.+ */+struct git_submodule {+ git_repository *owner;+ char *name;+ char *path; /* important: may point to same string data as "name" */+ char *url;+ uint32_t flags;+ git_oid head_oid;+ git_oid index_oid;+ git_oid wd_oid;+ /* information from config */+ git_submodule_update_t update;+ git_submodule_update_t update_default;+ git_submodule_ignore_t ignore;+ git_submodule_ignore_t ignore_default;+ int fetch_recurse;+ /* internal information */+ int refcount;+};++/* Additional flags on top of public GIT_SUBMODULE_STATUS values */+enum {+ GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20),+ GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21),+ GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22),+ GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23),+ GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24),+ GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25),+ GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26),+ GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27),+};++#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \+ ((S) & ~(0xFFFFFFFFu << 20))++#endif
libgit2/src/tag.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -22,41 +22,41 @@ git__free(tag); } -const git_oid *git_tag_id(git_tag *c)+const git_oid *git_tag_id(const git_tag *c) {- return git_object_id((git_object *)c);+ return git_object_id((const git_object *)c); } -int git_tag_target(git_object **target, git_tag *t)+int git_tag_target(git_object **target, const git_tag *t) { assert(t); return git_object_lookup(target, t->object.repo, &t->target, t->type); } -const git_oid *git_tag_target_oid(git_tag *t)+const git_oid *git_tag_target_id(const git_tag *t) { assert(t); return &t->target; } -git_otype git_tag_type(git_tag *t)+git_otype git_tag_target_type(const git_tag *t) { assert(t); return t->type; } -const char *git_tag_name(git_tag *t)+const char *git_tag_name(const git_tag *t) { assert(t); return t->tag_name; } -const git_signature *git_tag_tagger(git_tag *t)+const git_signature *git_tag_tagger(const git_tag *t) { return t->tagger; } -const char *git_tag_message(git_tag *t)+const char *git_tag_message(const git_tag *t) { assert(t); return t->message;@@ -131,7 +131,7 @@ buffer = search + 1; tag->tagger = NULL;- if (*buffer != '\n') {+ if (buffer < buffer_end && *buffer != '\n') { tag->tagger = git__malloc(sizeof(git_signature)); GITERR_CHECK_ALLOC(tag->tagger); @@ -139,16 +139,19 @@ return -1; } - if( *buffer != '\n' )- return tag_error("No new line before message");+ tag->message = NULL;+ if (buffer < buffer_end) {+ if( *buffer != '\n' )+ return tag_error("No new line before message"); - text_len = buffer_end - ++buffer;+ text_len = buffer_end - ++buffer; - tag->message = git__malloc(text_len + 1);- GITERR_CHECK_ALLOC(tag->message);+ tag->message = git__malloc(text_len + 1);+ GITERR_CHECK_ALLOC(tag->message); - memcpy(tag->message, buffer, text_len);- tag->message[text_len] = '\0';+ memcpy(tag->message, buffer, text_len);+ tag->message[text_len] = '\0';+ } return 0; }@@ -185,7 +188,7 @@ if (git_buf_joinpath(ref_name_out, GIT_REFS_TAGS_DIR, tag_name) < 0) return -1; - return git_reference_name_to_oid(oid, repo, ref_name_out->ptr);+ return git_reference_name_to_id(oid, repo, ref_name_out->ptr); } static int write_tag_annotation(@@ -196,7 +199,7 @@ const git_signature *tagger, const char *message) {- git_buf tag = GIT_BUF_INIT, cleaned_message = GIT_BUF_INIT;+ git_buf tag = GIT_BUF_INIT; git_odb *odb; git_oid__writebuf(&tag, "object ", git_object_id(target));@@ -205,15 +208,9 @@ git_signature__writebuf(&tag, "tagger ", tagger); git_buf_putc(&tag, '\n'); - /* Remove comments by default */- if (git_message_prettify(&cleaned_message, message, 1) < 0)- goto on_error;-- if (git_buf_puts(&tag, git_buf_cstr(&cleaned_message)) < 0)+ if (git_buf_puts(&tag, message) < 0) goto on_error; - git_buf_free(&cleaned_message);- if (git_repository_odb__weakptr(&odb, repo) < 0) goto on_error; @@ -225,7 +222,6 @@ on_error: git_buf_free(&tag);- git_buf_free(&cleaned_message); giterr_set(GITERR_OBJECT, "Failed to create tag annotation."); return -1; }@@ -255,7 +251,7 @@ error = retrieve_tag_reference_oid(oid, &ref_name, repo, tag_name); if (error < 0 && error != GIT_ENOTFOUND)- return -1;+ goto cleanup; /** Ensure the tag name doesn't conflict with an already existing * reference unless overwriting has explictly been requested **/@@ -271,8 +267,9 @@ } else git_oid_cpy(oid, git_object_id(target)); - error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite);+ error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); +cleanup: git_reference_free(new_ref); git_buf_free(&ref_name); return error;@@ -362,7 +359,7 @@ return -1; } - error = git_reference_create_oid(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite);+ error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite); git_reference_free(new_ref); git_buf_free(&ref_name);@@ -379,18 +376,21 @@ int git_tag_delete(git_repository *repo, const char *tag_name) {- int error; git_reference *tag_ref; git_buf ref_name = GIT_BUF_INIT;+ int error; error = retrieve_tag_reference(&tag_ref, &ref_name, repo, tag_name); git_buf_free(&ref_name); if (error < 0)- return -1;+ return error; - return git_reference_delete(tag_ref);+ if ((error = git_reference_delete(tag_ref)) == 0)+ git_reference_free(tag_ref);+ + return error; } int git_tag__parse(git_tag *tag, git_odb_object *obj)@@ -400,22 +400,52 @@ } typedef struct {- git_vector *taglist;- const char *pattern;+ git_repository *repo;+ git_tag_foreach_cb cb;+ void *cb_data;+} tag_cb_data;++static int tags_cb(const char *ref, void *data)+{+ git_oid oid;+ tag_cb_data *d = (tag_cb_data *)data;++ if (git__prefixcmp(ref, GIT_REFS_TAGS_DIR) != 0)+ return 0; /* no tag */++ if (git_reference_name_to_id(&oid, d->repo, ref) < 0)+ return -1;++ return d->cb(ref, &oid, d->cb_data);+}++int git_tag_foreach(git_repository *repo, git_tag_foreach_cb cb, void *cb_data)+{+ tag_cb_data data;++ assert(repo && cb);++ data.cb = cb;+ data.cb_data = cb_data;+ data.repo = repo;++ return git_reference_foreach(repo, GIT_REF_OID, &tags_cb, &data);+}++typedef struct {+ git_vector *taglist;+ const char *pattern; } tag_filter_data; #define GIT_REFS_TAGS_DIR_LEN strlen(GIT_REFS_TAGS_DIR) -static int tag_list_cb(const char *tag_name, void *payload)+static int tag_list_cb(const char *tag_name, git_oid *oid, void *data) {- tag_filter_data *filter;-- if (git__prefixcmp(tag_name, GIT_REFS_TAGS_DIR) != 0)- return 0;+ tag_filter_data *filter = (tag_filter_data *)data;+ GIT_UNUSED(oid); - filter = (tag_filter_data *)payload; if (!*filter->pattern || p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0)- return git_vector_insert(filter->taglist, git__strdup(tag_name));+ return git_vector_insert(filter->taglist, git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN)); return 0; }@@ -434,7 +464,7 @@ filter.taglist = &taglist; filter.pattern = pattern; - error = git_reference_foreach(repo, GIT_REF_OID|GIT_REF_PACKED, &tag_list_cb, (void *)&filter);+ error = git_tag_foreach(repo, &tag_list_cb, (void *)&filter); if (error < 0) { git_vector_free(&taglist); return -1;@@ -450,22 +480,7 @@ return git_tag_list_match(tag_names, "", repo); } -int git_tag_peel(git_object **tag_target, git_tag *tag)+int git_tag_peel(git_object **tag_target, const git_tag *tag) {- int error;- git_object *target;-- assert(tag_target && tag);-- if (git_tag_target(&target, tag) < 0)- return -1;-- if (git_object_type(target) == GIT_OBJ_TAG) {- error = git_tag_peel(tag_target, (git_tag *)target);- git_object_free(target);- return error;- }-- *tag_target = target;- return 0;+ return git_object_peel(tag_target, (const git_object *)tag, GIT_OBJ_ANY); }
libgit2/src/tag.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/thread-utils.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/thread-utils.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -38,13 +38,13 @@ #define git_mutex_unlock(a) pthread_mutex_unlock(a) #define git_mutex_free(a) pthread_mutex_destroy(a) -/* Pthreads condition vars -- disabled by now */-#define git_cond unsigned int //pthread_cond_t-#define git_cond_init(c, a) (void)0 //pthread_cond_init(c, a)-#define git_cond_free(c) (void)0 //pthread_cond_destroy(c)-#define git_cond_wait(c, l) (void)0 //pthread_cond_wait(c, l)-#define git_cond_signal(c) (void)0 //pthread_cond_signal(c)-#define git_cond_broadcast(c) (void)0 //pthread_cond_broadcast(c)+/* Pthreads condition vars */+#define git_cond pthread_cond_t+#define git_cond_init(c) pthread_cond_init(c, NULL)+#define git_cond_free(c) pthread_cond_destroy(c)+#define git_cond_wait(c, l) pthread_cond_wait(c, l)+#define git_cond_signal(c) pthread_cond_signal(c)+#define git_cond_broadcast(c) pthread_cond_broadcast(c) GIT_INLINE(int) git_atomic_inc(git_atomic *a) {@@ -79,7 +79,7 @@ /* Pthreads Mutex */ #define git_mutex unsigned int #define git_mutex_init(a) (void)0-#define git_mutex_lock(a) (void)0+#define git_mutex_lock(a) 0 #define git_mutex_unlock(a) (void)0 #define git_mutex_free(a) (void)0
+ libgit2/src/trace.c view
@@ -0,0 +1,39 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "buffer.h"+#include "common.h"+#include "global.h"+#include "trace.h"+#include "git2/trace.h"++#ifdef GIT_TRACE++struct git_trace_data git_trace__data = {0};++#endif++int git_trace_set(git_trace_level_t level, git_trace_callback callback)+{+#ifdef GIT_TRACE+ assert(level == 0 || callback != NULL);++ git_trace__data.level = level;+ git_trace__data.callback = callback;+ GIT_MEMORY_BARRIER;+ + return 0;+#else+ GIT_UNUSED(level);+ GIT_UNUSED(callback);++ giterr_set(GITERR_INVALID,+ "This version of libgit2 was not built with tracing.");+ return -1;+#endif+}+
+ libgit2/src/trace.h view
@@ -0,0 +1,56 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_trace_h__+#define INCLUDE_trace_h__++#include <stdarg.h>++#include <git2/trace.h>+#include "buffer.h"++#ifdef GIT_TRACE++struct git_trace_data {+ git_trace_level_t level;+ git_trace_callback callback;+};++extern struct git_trace_data git_trace__data;++GIT_INLINE(void) git_trace__write_fmt(+ git_trace_level_t level,+ const char *fmt, ...)+{+ git_trace_callback callback = git_trace__data.callback; + git_buf message = GIT_BUF_INIT;+ va_list ap;+ + va_start(ap, fmt);+ git_buf_vprintf(&message, fmt, ap);+ va_end(ap);+ + callback(level, git_buf_cstr(&message));++ git_buf_free(&message);+}++#define git_trace_level() (git_trace__data.level)+#define git_trace(l, ...) { \+ if (git_trace__data.level >= l && \+ git_trace__data.callback != NULL) { \+ git_trace__write_fmt(l, __VA_ARGS__); \+ } \+ }++#else++#define git_trace_level() ((void)0)+#define git_trace(lvl, ...) ((void)0)++#endif++#endif
libgit2/src/transport.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,76 +8,116 @@ #include "git2/types.h" #include "git2/remote.h" #include "git2/net.h"-#include "transport.h"+#include "git2/transport.h" #include "path.h" -static struct {+typedef struct transport_definition { char *prefix;+ unsigned priority; git_transport_cb fn;-} transports[] = {- {"git://", git_transport_git},- {"http://", git_transport_http},- {"https://", git_transport_dummy},- {"file://", git_transport_local},- {"git+ssh://", git_transport_dummy},- {"ssh+git://", git_transport_dummy},- {NULL, 0}+ void *param;+} transport_definition;++static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };+static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };++static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 };+static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 };++static transport_definition transports[] = {+ {"git://", 1, git_transport_smart, &git_subtransport_definition},+ {"http://", 1, git_transport_smart, &http_subtransport_definition},+ {"https://", 1, git_transport_smart, &http_subtransport_definition},+ {"file://", 1, git_transport_local, NULL},+ {"git+ssh://", 1, git_transport_dummy, NULL},+ {"ssh+git://", 1, git_transport_dummy, NULL},+ {NULL, 0, 0} }; #define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 -static git_transport_cb transport_find_fn(const char *url)+static int transport_find_fn(const char *url, git_transport_cb *callback, void **param) { size_t i = 0;+ unsigned priority = 0;+ transport_definition *definition = NULL, *definition_iter; // First, check to see if it's an obvious URL, which a URL scheme for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) {- if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix)))- return transports[i].fn;+ definition_iter = &transports[i];++ if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix)))+ continue;++ if (definition_iter->priority > priority)+ definition = definition_iter; } - /* still here? Check to see if the path points to a file on the local file system */- if (git_path_exists(url) && git_path_isdir(url))- return &git_transport_local;+#ifdef GIT_WIN32+ /* On Windows, it might not be possible to discern between absolute local+ * and ssh paths - first check if this is a valid local path that points+ * to a directory and if so assume local path, else assume SSH */ - /* It could be a SSH remote path. Check to see if there's a : */- if (strrchr(url, ':'))- return &git_transport_dummy; /* SSH is an unsupported transport mechanism in this version of libgit2 */+ /* Check to see if the path points to a file on the local file system */+ if (!definition && git_path_exists(url) && git_path_isdir(url))+ definition = &local_transport_definition; - return NULL;+ /* It could be a SSH remote path. Check to see if there's a :+ * SSH is an unsupported transport mechanism in this version of libgit2 */+ if (!definition && strrchr(url, ':'))+ definition = &dummy_transport_definition; +#else+ /* For other systems, perform the SSH check first, to avoid going to the+ * filesystem if it is not necessary */++ /* It could be a SSH remote path. Check to see if there's a :+ * SSH is an unsupported transport mechanism in this version of libgit2 */+ if (!definition && strrchr(url, ':'))+ definition = &dummy_transport_definition;++ /* Check to see if the path points to a file on the local file system */+ if (!definition && git_path_exists(url) && git_path_isdir(url))+ definition = &local_transport_definition;+#endif++ if (!definition)+ return -1;++ *callback = definition->fn;+ *param = definition->param;+ + return 0; } /************** * Public API * **************/ -int git_transport_dummy(git_transport **transport)+int git_transport_dummy(git_transport **transport, git_remote *owner, void *param) { GIT_UNUSED(transport);+ GIT_UNUSED(owner);+ GIT_UNUSED(param); giterr_set(GITERR_NET, "This transport isn't implemented. Sorry"); return -1; } -int git_transport_new(git_transport **out, const char *url)+int git_transport_new(git_transport **out, git_remote *owner, const char *url) { git_transport_cb fn; git_transport *transport;+ void *param; int error; - fn = transport_find_fn(url);-- if (fn == NULL) {+ if (transport_find_fn(url, &fn, ¶m) < 0) { giterr_set(GITERR_NET, "Unsupported URL protocol"); return -1; } - error = fn(&transport);+ error = fn(&transport, owner, param); if (error < 0) return error; - transport->url = git__strdup(url);- GITERR_CHECK_ALLOC(transport->url);- *out = transport; return 0;@@ -86,12 +126,19 @@ /* from remote.h */ int git_remote_valid_url(const char *url) {- return transport_find_fn(url) != NULL;+ git_transport_cb fn;+ void *param;++ return !transport_find_fn(url, &fn, ¶m); } int git_remote_supported_url(const char* url) {- git_transport_cb transport_fn = transport_find_fn(url);+ git_transport_cb fn;+ void *param; - return ((transport_fn != NULL) && (transport_fn != &git_transport_dummy));+ if (transport_find_fn(url, &fn, ¶m) < 0)+ return 0;++ return fn != &git_transport_dummy; }
− libgit2/src/transport.h
@@ -1,108 +0,0 @@-/*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */-#ifndef INCLUDE_transport_h__-#define INCLUDE_transport_h__--#include "git2/net.h"-#include "git2/indexer.h"-#include "vector.h"--#define GIT_CAP_OFS_DELTA "ofs-delta"--typedef struct git_transport_caps {- int common:1,- ofs_delta:1;-} git_transport_caps;--/*- * A day in the life of a network operation- * ========================================- *- * The library gets told to ls-remote/push/fetch on/to/from some- * remote. We look at the URL of the remote and fill the function- * table with whatever is appropriate (the remote may be git over git,- * ssh or http(s). It may even be an hg or svn repository, the library- * at this level doesn't care, it just calls the helpers.- *- * The first call is to ->connect() which connects to the remote,- * making use of the direction if necessary. This function must also- * store the remote heads and any other information it needs.- *- * The next useful step is to call ->ls() to get the list of- * references available to the remote. These references may have been- * collected on connect, or we may build them now. For ls-remote,- * nothing else is needed other than closing the connection.- * Otherwise, the higher leves decide which objects we want to- * have. ->send_have() is used to tell the other end what we have. If- * we do need to download a pack, ->download_pack() is called.- *- * When we're done, we call ->close() to close the- * connection. ->free() takes care of freeing all the resources.- */--struct git_transport {- /**- * Where the repo lives- */- char *url;- /**- * Whether we want to push or fetch- */- int direction : 1, /* 0 fetch, 1 push */- connected : 1;- /**- * Connect and store the remote heads- */- int (*connect)(struct git_transport *transport, int dir);- /**- * Give a list of references, useful for ls-remote- */- int (*ls)(struct git_transport *transport, git_headlist_cb list_cb, void *opaque);- /**- * Push the changes over- */- int (*push)(struct git_transport *transport);- /**- * Negotiate the minimal amount of objects that need to be- * retrieved- */- int (*negotiate_fetch)(struct git_transport *transport, git_repository *repo, const git_vector *wants);- /**- * Download the packfile- */- int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats);- /**- * Fetch the changes- */- int (*fetch)(struct git_transport *transport);- /**- * Close the connection- */- int (*close)(struct git_transport *transport);- /**- * Free the associated resources- */- void (*free)(struct git_transport *transport);-};---int git_transport_new(struct git_transport **transport, const char *url);-int git_transport_local(struct git_transport **transport);-int git_transport_git(struct git_transport **transport);-int git_transport_http(struct git_transport **transport);-int git_transport_dummy(struct git_transport **transport);--/**- Returns true if the passed URL is valid (a URL with a Git supported scheme,- or pointing to an existing path)-*/-int git_transport_valid_url(const char *url);--typedef struct git_transport git_transport;-typedef int (*git_transport_cb)(git_transport **transport);--#endif
+ libgit2/src/transports/cred.c view
@@ -0,0 +1,60 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "git2.h"+#include "smart.h"+#include "git2/cred_helpers.h"++static void plaintext_free(struct git_cred *cred)+{+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;+ size_t pass_len = strlen(c->password);++ git__free(c->username);++ /* Zero the memory which previously held the password */+ memset(c->password, 0x0, pass_len);+ git__free(c->password);++ memset(c, 0, sizeof(*c));++ git__free(c);+}++int git_cred_userpass_plaintext_new(+ git_cred **cred,+ const char *username,+ const char *password)+{+ git_cred_userpass_plaintext *c;++ if (!cred)+ return -1;++ c = git__malloc(sizeof(git_cred_userpass_plaintext));+ GITERR_CHECK_ALLOC(c);++ c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT;+ c->parent.free = plaintext_free;+ c->username = git__strdup(username);++ if (!c->username) {+ git__free(c);+ return -1;+ }++ c->password = git__strdup(password);++ if (!c->password) {+ git__free(c->username);+ git__free(c);+ return -1;+ }++ *cred = &c->parent;+ return 0;+}
+ libgit2/src/transports/cred_helpers.c view
@@ -0,0 +1,49 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "common.h"+#include "git2/cred_helpers.h"++int git_cred_userpass(+ git_cred **cred,+ const char *url,+ const char *user_from_url,+ unsigned int allowed_types,+ void *payload)+{+ git_cred_userpass_payload *userpass = (git_cred_userpass_payload*)payload;+ const char *effective_username = NULL;++ GIT_UNUSED(url);++ if (!userpass || !userpass->password) return -1;++ /* Username resolution: a username can be passed with the URL, the+ * credentials payload, or both. Here's what we do. Note that if we get+ * this far, we know that any password the url may contain has already+ * failed at least once, so we ignore it.+ *+ * | Payload | URL | Used |+ * +-------------+----------+-----------++ * | yes | no | payload |+ * | yes | yes | payload |+ * | no | yes | url |+ * | no | no | FAIL |+ */+ if (userpass->username)+ effective_username = userpass->username;+ else if (user_from_url)+ effective_username = user_from_url;+ else+ return -1;++ if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 ||+ git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0)+ return -1;++ return 0;+}
libgit2/src/transports/git.c view
@@ -1,50 +1,42 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ -#include "git2/net.h"-#include "git2/common.h"-#include "git2/types.h"-#include "git2/errors.h"-#include "git2/net.h"-#include "git2/revwalk.h"--#include "vector.h"-#include "transport.h"-#include "pkt.h"-#include "common.h"+#include "git2.h"+#include "buffer.h" #include "netops.h"-#include "filebuf.h"-#include "repository.h"-#include "fetch.h"-#include "protocol.h" +#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)++static const char prefix_git[] = "git://";+static const char cmd_uploadpack[] = "git-upload-pack";+static const char cmd_receivepack[] = "git-receive-pack";+ typedef struct {- git_transport parent;- git_protocol proto;- GIT_SOCKET socket;- git_vector refs;- git_remote_head **heads;- git_transport_caps caps;- char buff[1024];- gitno_buffer buf;-#ifdef GIT_WIN32- WSADATA wsd;-#endif-} transport_git;+ git_smart_subtransport_stream parent;+ gitno_socket socket;+ const char *cmd;+ char *url;+ unsigned sent_command : 1;+} git_stream; +typedef struct {+ git_smart_subtransport parent;+ git_transport *owner;+ git_stream *current_stream;+} git_subtransport;+ /*- * Create a git procol request.+ * Create a git protocol request. * * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 */ static int gen_proto(git_buf *request, const char *cmd, const char *url) { char *delim, *repo;- char default_command[] = "git-upload-pack"; char host[] = "host="; size_t len; @@ -60,9 +52,6 @@ if (delim == NULL) delim = strchr(url, '/'); - if (cmd == NULL)- cmd = default_command;- len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; git_buf_grow(request, len);@@ -77,402 +66,287 @@ return 0; } -static int send_request(GIT_SOCKET s, const char *cmd, const char *url)+static int send_command(git_stream *s) { int error; git_buf request = GIT_BUF_INIT; - error = gen_proto(&request, cmd, url);+ error = gen_proto(&request, s->cmd, s->url); if (error < 0) goto cleanup; - error = gitno_send(s, request.ptr, request.size, 0);+ /* It looks like negative values are errors here, and positive values+ * are the number of bytes sent. */+ error = gitno_send(&s->socket, request.ptr, request.size, 0); + if (error >= 0)+ s->sent_command = 1;+ cleanup: git_buf_free(&request); return error; } -/*- * Parse the URL and connect to a server, storing the socket in- * out. For convenience this also takes care of asking for the remote- * refs- */-static int do_connect(transport_git *t, const char *url)+static int git_stream_read(+ git_smart_subtransport_stream *stream,+ char *buffer,+ size_t buf_size,+ size_t *bytes_read) {- char *host, *port;- const char prefix[] = "git://";- int error;-- t->socket = INVALID_SOCKET;+ git_stream *s = (git_stream *)stream;+ gitno_buffer buf; - if (!git__prefixcmp(url, prefix))- url += strlen(prefix);+ *bytes_read = 0; - if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)+ if (!s->sent_command && send_command(s) < 0) return -1; - if ((error = gitno_connect(&t->socket, host, port)) == 0) {- error = send_request(t->socket, NULL, url);- }-- git__free(host);- git__free(port);-- if (error < 0 && t->socket != INVALID_SOCKET) {- gitno_close(t->socket);- t->socket = INVALID_SOCKET;- }+ gitno_buffer_setup(&s->socket, &buf, buffer, buf_size); - if (t->socket == INVALID_SOCKET) {- giterr_set(GITERR_NET, "Failed to connect to the host");+ if (gitno_recv(&buf) < 0) return -1;- } + *bytes_read = buf.offset;+ return 0; } -/*- * Read from the socket and store the references in the vector- */-static int store_refs(transport_git *t)+static int git_stream_write(+ git_smart_subtransport_stream *stream,+ const char *buffer,+ size_t len) {- gitno_buffer *buf = &t->buf;- int ret = 0;-- while (1) {- if ((ret = gitno_recv(buf)) < 0)- return -1;- if (ret == 0) /* Orderly shutdown, so exit */- return 0;-- ret = git_protocol_store_refs(&t->proto, buf->data, buf->offset);- if (ret == GIT_EBUFS) {- gitno_consume_n(buf, buf->len);- continue;- }-- if (ret < 0)- return ret;+ git_stream *s = (git_stream *)stream; - gitno_consume_n(buf, buf->offset);+ if (!s->sent_command && send_command(s) < 0)+ return -1; - if (t->proto.flush) { /* No more refs */- t->proto.flush = 0;- return 0;- }- }+ return gitno_send(&s->socket, buffer, len, 0); } -static int detect_caps(transport_git *t)+static void git_stream_free(git_smart_subtransport_stream *stream) {- git_vector *refs = &t->refs;- git_pkt_ref *pkt;- git_transport_caps *caps = &t->caps;- const char *ptr;-- pkt = git_vector_get(refs, 0);- /* No refs or capabilites, odd but not a problem */- if (pkt == NULL || pkt->capabilities == NULL)- return 0;+ git_stream *s = (git_stream *)stream;+ git_subtransport *t = OWNING_SUBTRANSPORT(s);+ int ret; - ptr = pkt->capabilities;- while (ptr != NULL && *ptr != '\0') {- if (*ptr == ' ')- ptr++;+ GIT_UNUSED(ret); - if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {- caps->common = caps->ofs_delta = 1;- ptr += strlen(GIT_CAP_OFS_DELTA);- continue;- }+ t->current_stream = NULL; - /* We don't know this capability, so skip it */- ptr = strchr(ptr, ' ');+ if (s->socket.socket) {+ ret = gitno_close(&s->socket);+ assert(!ret); } - return 0;+ git__free(s->url);+ git__free(s); } -/*- * Since this is a network connection, we need to parse and store the- * pkt-lines at this stage and keep them there.- */-static int git_connect(git_transport *transport, int direction)+static int git_stream_alloc(+ git_subtransport *t,+ const char *url,+ const char *cmd,+ git_smart_subtransport_stream **stream) {- transport_git *t = (transport_git *) transport;-- if (direction == GIT_DIR_PUSH) {- giterr_set(GITERR_NET, "Pushing over git:// is not supported");- return -1;- }+ git_stream *s; - t->parent.direction = direction;- if (git_vector_init(&t->refs, 16, NULL) < 0)+ if (!stream) return -1; - /* Connect and ask for the refs */- if (do_connect(t, transport->url) < 0)- goto cleanup;+ s = git__calloc(sizeof(git_stream), 1);+ GITERR_CHECK_ALLOC(s); - gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket);+ s->parent.subtransport = &t->parent;+ s->parent.read = git_stream_read;+ s->parent.write = git_stream_write;+ s->parent.free = git_stream_free; - t->parent.connected = 1;- if (store_refs(t) < 0)- goto cleanup;+ s->cmd = cmd;+ s->url = git__strdup(url); - if (detect_caps(t) < 0)- goto cleanup;+ if (!s->url) {+ git__free(s);+ return -1;+ } + *stream = &s->parent; return 0;-cleanup:- git_vector_free(&t->refs);- return -1; } -static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque)+static int _git_uploadpack_ls(+ git_subtransport *t,+ const char *url,+ git_smart_subtransport_stream **stream) {- transport_git *t = (transport_git *) transport;- git_vector *refs = &t->refs;- unsigned int i;- git_pkt *p = NULL;-- git_vector_foreach(refs, i, p) {- git_pkt_ref *pkt = NULL;-- if (p->type != GIT_PKT_REF)- continue;+ char *host, *port, *user=NULL, *pass=NULL;+ git_stream *s; - pkt = (git_pkt_ref *)p;+ *stream = NULL; - if (list_cb(&pkt->head, opaque) < 0) {- giterr_set(GITERR_NET, "User callback returned error");- return -1;- }- }+ if (!git__prefixcmp(url, prefix_git))+ url += strlen(prefix_git); - return 0;-}+ if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0)+ return -1; -/* Wait until we get an ack from the */-static int recv_pkt(gitno_buffer *buf)-{- const char *ptr = buf->data, *line_end;- git_pkt *pkt;- int pkt_type, error;+ s = (git_stream *)*stream; - do {- /* Wait for max. 1 second */- if ((error = gitno_select_in(buf, 1, 0)) < 0) {- return -1;- } else if (error == 0) {- /*- * Some servers don't respond immediately, so if this- * happens, we keep sending information until it- * answers. Pretend we received a NAK to convince higher- * layers to do so.- */- return GIT_PKT_NAK;- }+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)+ goto on_error; - if ((error = gitno_recv(buf)) < 0)- return -1;+ if (gitno_connect(&s->socket, host, port, 0) < 0)+ goto on_error; - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);- if (error == GIT_EBUFS)- continue;- if (error < 0)- return -1;- } while (error);+ t->current_stream = s;+ git__free(host);+ git__free(port);+ git__free(user);+ git__free(pass);+ return 0; - gitno_consume(buf, line_end);- pkt_type = pkt->type;- git__free(pkt);+on_error:+ if (*stream)+ git_stream_free(*stream); - return pkt_type;+ git__free(host);+ git__free(port);+ return -1; } -static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)+static int _git_uploadpack(+ git_subtransport *t,+ const char *url,+ git_smart_subtransport_stream **stream) {- transport_git *t = (transport_git *) transport;- git_revwalk *walk;- git_oid oid;- int error;- unsigned int i;- git_buf data = GIT_BUF_INIT;- gitno_buffer *buf = &t->buf;-- if (git_pkt_buffer_wants(wants, &t->caps, &data) < 0)- return -1;+ GIT_UNUSED(url); - if (git_fetch_setup_walk(&walk, repo) < 0)- goto on_error;+ if (t->current_stream) {+ *stream = &t->current_stream->parent;+ return 0;+ } - if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)- goto on_error;+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");+ return -1;+} - git_buf_clear(&data);- /*- * We don't support any kind of ACK extensions, so the negotiation- * boils down to sending what we have and listening for an ACK- * every once in a while.- */- i = 0;- while ((error = git_revwalk_next(&oid, walk)) == 0) {- git_pkt_buffer_have(&oid, &data);- i++;- if (i % 20 == 0) {- int pkt_type;+static int _git_receivepack_ls(+ git_subtransport *t,+ const char *url,+ git_smart_subtransport_stream **stream)+{+ char *host, *port, *user=NULL, *pass=NULL;+ git_stream *s; - git_pkt_buffer_flush(&data);- if (git_buf_oom(&data))- goto on_error;+ *stream = NULL; - if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)- goto on_error;+ if (!git__prefixcmp(url, prefix_git))+ url += strlen(prefix_git); - pkt_type = recv_pkt(buf);+ if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0)+ return -1; - if (pkt_type == GIT_PKT_ACK) {- break;- } else if (pkt_type == GIT_PKT_NAK) {- continue;- } else {- giterr_set(GITERR_NET, "Unexpected pkt type");- goto on_error;- }+ s = (git_stream *)*stream; - }- }- if (error < 0 && error != GIT_REVWALKOVER)+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0) goto on_error; - /* Tell the other end that we're done negotiating */- git_buf_clear(&data);- git_pkt_buffer_flush(&data);- git_pkt_buffer_done(&data);- if (gitno_send(t->socket, data.ptr, data.size, 0) < 0)+ if (gitno_connect(&s->socket, host, port, 0) < 0) goto on_error; - git_buf_free(&data);- git_revwalk_free(walk);+ t->current_stream = s;+ git__free(host);+ git__free(port);+ git__free(user);+ git__free(pass); return 0; on_error:- git_buf_free(&data);- git_revwalk_free(walk);+ if (*stream)+ git_stream_free(*stream);++ git__free(host);+ git__free(port); return -1; } -static int git_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats)+static int _git_receivepack(+ git_subtransport *t,+ const char *url,+ git_smart_subtransport_stream **stream) {- transport_git *t = (transport_git *) transport;- int error = 0, read_bytes;- gitno_buffer *buf = &t->buf;- git_pkt *pkt;- const char *line_end, *ptr;+ GIT_UNUSED(url); - /*- * For now, we ignore everything and wait for the pack- */- do {- ptr = buf->data;- /* Whilst we're searching for the pack */- while (1) {- if (buf->offset == 0) {- break;- }+ if (t->current_stream) {+ *stream = &t->current_stream->parent;+ return 0;+ } - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);- if (error == GIT_EBUFS)- break;+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");+ return -1;+} - if (error < 0)- return error;+static int _git_action(+ git_smart_subtransport_stream **stream,+ git_smart_subtransport *subtransport,+ const char *url,+ git_smart_service_t action)+{+ git_subtransport *t = (git_subtransport *) subtransport; - if (pkt->type == GIT_PKT_PACK) {- git__free(pkt);- return git_fetch__download_pack(buf->data, buf->offset, t->socket, repo, bytes, stats);- }+ switch (action) {+ case GIT_SERVICE_UPLOADPACK_LS:+ return _git_uploadpack_ls(t, url, stream); - /* For now we don't care about anything */- git__free(pkt);- gitno_consume(buf, line_end);- }+ case GIT_SERVICE_UPLOADPACK:+ return _git_uploadpack(t, url, stream); - read_bytes = gitno_recv(buf);- } while (read_bytes);+ case GIT_SERVICE_RECEIVEPACK_LS:+ return _git_receivepack_ls(t, url, stream); - return read_bytes;+ case GIT_SERVICE_RECEIVEPACK:+ return _git_receivepack(t, url, stream);+ }++ *stream = NULL;+ return -1; } -static int git_close(git_transport *transport)+static int _git_close(git_smart_subtransport *subtransport) {- transport_git *t = (transport_git*) transport;+ git_subtransport *t = (git_subtransport *) subtransport; - /* Can't do anything if there's an error, so don't bother checking */- git_pkt_send_flush(t->socket);- if (gitno_close(t->socket) < 0) {- giterr_set(GITERR_NET, "Failed to close socket");- return -1;- }+ assert(!t->current_stream); -#ifdef GIT_WIN32- WSACleanup();-#endif+ GIT_UNUSED(t); return 0; } -static void git_free(git_transport *transport)+static void _git_free(git_smart_subtransport *subtransport) {- transport_git *t = (transport_git *) transport;- git_vector *refs = &t->refs;- unsigned int i;+ git_subtransport *t = (git_subtransport *) subtransport; - for (i = 0; i < refs->length; ++i) {- git_pkt *p = git_vector_get(refs, i);- git_pkt_free(p);- }+ assert(!t->current_stream); - git_vector_free(refs);- git__free(t->heads);- git_buf_free(&t->proto.buf);- git__free(t->parent.url); git__free(t); } -int git_transport_git(git_transport **out)+int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner) {- transport_git *t;-#ifdef GIT_WIN32- int ret;-#endif-- t = git__malloc(sizeof(transport_git));- GITERR_CHECK_ALLOC(t);-- memset(t, 0x0, sizeof(transport_git));+ git_subtransport *t; - t->parent.connect = git_connect;- t->parent.ls = git_ls;- t->parent.negotiate_fetch = git_negotiate_fetch;- t->parent.download_pack = git_download_pack;- t->parent.close = git_close;- t->parent.free = git_free;- t->proto.refs = &t->refs;- t->proto.transport = (git_transport *) t;+ if (!out)+ return -1; - *out = (git_transport *) t;+ t = git__calloc(sizeof(git_subtransport), 1);+ GITERR_CHECK_ALLOC(t); -#ifdef GIT_WIN32- ret = WSAStartup(MAKEWORD(2,2), &t->wsd);- if (ret != 0) {- git_free(*out);- giterr_set(GITERR_NET, "Winsock init failed");- return -1;- }-#endif+ t->owner = owner;+ t->parent.action = _git_action;+ t->parent.close = _git_close;+ t->parent.free = _git_free; + *out = (git_smart_subtransport *) t; return 0; }
libgit2/src/transports/http.c view
@@ -1,682 +1,953 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors- *- * This file is part of libgit2, distributed under the GNU GPL v2 with- * a Linking Exception. For full terms see the included COPYING file.- */--#include <stdlib.h>-#include "git2.h"-#include "http_parser.h"--#include "transport.h"-#include "common.h"-#include "netops.h"-#include "buffer.h"-#include "pkt.h"-#include "refs.h"-#include "pack.h"-#include "fetch.h"-#include "filebuf.h"-#include "repository.h"-#include "protocol.h"--enum last_cb {- NONE,- FIELD,- VALUE-};--typedef struct {- git_transport parent;- git_protocol proto;- git_vector refs;- git_vector common;- GIT_SOCKET socket;- git_buf buf;- git_remote_head **heads;- int error;- int transfer_finished :1,- ct_found :1,- ct_finished :1,- pack_ready :1;- enum last_cb last_cb;- http_parser parser;- char *content_type;- char *host;- char *port;- char *service;- git_transport_caps caps;-#ifdef GIT_WIN32- WSADATA wsd;-#endif-} transport_http;--static int gen_request(git_buf *buf, const char *url, const char *host, const char *op,- const char *service, ssize_t content_length, int ls)-{- const char *path = url;-- path = strchr(path, '/');- if (path == NULL) /* Is 'git fetch http://host.com/' valid? */- path = "/";-- if (ls) {- git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service);- } else {- git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service);- }- git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");- git_buf_printf(buf, "Host: %s\r\n", host);- if (content_length > 0) {- git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service);- git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service);- git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);- } else {- git_buf_puts(buf, "Accept: */*\r\n");- }- git_buf_puts(buf, "\r\n");-- if (git_buf_oom(buf))- return -1;-- return 0;-}--static int do_connect(transport_http *t, const char *host, const char *port)-{- GIT_SOCKET s;-- if (t->parent.connected && http_should_keep_alive(&t->parser))- return 0;-- if (gitno_connect(&s, host, port) < 0)- return -1;-- t->socket = s;- t->parent.connected = 1;-- return 0;-}--/*- * The HTTP parser is streaming, so we need to wait until we're in the- * field handler before we can be sure that we can store the previous- * value. Right now, we only care about the- * Content-Type. on_header_{field,value} should be kept generic enough- * to work for any request.- */--static const char *typestr = "Content-Type";--static int on_header_field(http_parser *parser, const char *str, size_t len)-{- transport_http *t = (transport_http *) parser->data;- git_buf *buf = &t->buf;-- if (t->last_cb == VALUE && t->ct_found) {- t->ct_finished = 1;- t->ct_found = 0;- t->content_type = git__strdup(git_buf_cstr(buf));- GITERR_CHECK_ALLOC(t->content_type);- git_buf_clear(buf);- }-- if (t->ct_found) {- t->last_cb = FIELD;- return 0;- }-- if (t->last_cb != FIELD)- git_buf_clear(buf);-- git_buf_put(buf, str, len);- t->last_cb = FIELD;-- return git_buf_oom(buf);-}--static int on_header_value(http_parser *parser, const char *str, size_t len)-{- transport_http *t = (transport_http *) parser->data;- git_buf *buf = &t->buf;-- if (t->ct_finished) {- t->last_cb = VALUE;- return 0;- }-- if (t->last_cb == VALUE)- git_buf_put(buf, str, len);-- if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {- t->ct_found = 1;- git_buf_clear(buf);- git_buf_put(buf, str, len);- }-- t->last_cb = VALUE;-- return git_buf_oom(buf);-}--static int on_headers_complete(http_parser *parser)-{- transport_http *t = (transport_http *) parser->data;- git_buf *buf = &t->buf;-- /* The content-type is text/plain for 404, so don't validate */- if (parser->status_code == 404) {- git_buf_clear(buf);- return 0;- }-- if (t->content_type == NULL) {- t->content_type = git__strdup(git_buf_cstr(buf));- if (t->content_type == NULL)- return t->error = -1;- }-- git_buf_clear(buf);- git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);- if (git_buf_oom(buf))- return t->error = -1;-- if (strcmp(t->content_type, git_buf_cstr(buf)))- return t->error = -1;-- git_buf_clear(buf);- return 0;-}--static int on_body_store_refs(http_parser *parser, const char *str, size_t len)-{- transport_http *t = (transport_http *) parser->data;-- if (parser->status_code == 404) {- return git_buf_put(&t->buf, str, len);- }-- return git_protocol_store_refs(&t->proto, str, len);-}--static int on_message_complete(http_parser *parser)-{- transport_http *t = (transport_http *) parser->data;-- t->transfer_finished = 1;-- if (parser->status_code == 404) {- giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf));- t->error = -1;- }-- return 0;-}--static int store_refs(transport_http *t)-{- http_parser_settings settings;- char buffer[1024];- gitno_buffer buf;- git_pkt *pkt;- int ret;-- http_parser_init(&t->parser, HTTP_RESPONSE);- t->parser.data = t;- memset(&settings, 0x0, sizeof(http_parser_settings));- settings.on_header_field = on_header_field;- settings.on_header_value = on_header_value;- settings.on_headers_complete = on_headers_complete;- settings.on_body = on_body_store_refs;- settings.on_message_complete = on_message_complete;-- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);-- while(1) {- size_t parsed;-- if ((ret = gitno_recv(&buf)) < 0)- return -1;-- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);- /* Both should happen at the same time */- if (parsed != buf.offset || t->error < 0)- return t->error;-- gitno_consume_n(&buf, parsed);-- if (ret == 0 || t->transfer_finished)- return 0;- }-- pkt = git_vector_get(&t->refs, 0);- if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) {- giterr_set(GITERR_NET, "Invalid HTTP response");- return t->error = -1;- } else {- git_vector_remove(&t->refs, 0);- }-- return 0;-}--static int http_connect(git_transport *transport, int direction)-{- transport_http *t = (transport_http *) transport;- int ret;- git_buf request = GIT_BUF_INIT;- const char *service = "upload-pack";- const char *url = t->parent.url, *prefix = "http://";-- if (direction == GIT_DIR_PUSH) {- giterr_set(GITERR_NET, "Pushing over HTTP is not implemented");- return -1;- }-- t->parent.direction = direction;- if (git_vector_init(&t->refs, 16, NULL) < 0)- return -1;-- if (!git__prefixcmp(url, prefix))- url += strlen(prefix);-- if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, "80")) < 0)- goto cleanup;-- t->service = git__strdup(service);- GITERR_CHECK_ALLOC(t->service);-- if ((ret = do_connect(t, t->host, t->port)) < 0)- goto cleanup;-- /* Generate and send the HTTP request */- if ((ret = gen_request(&request, url, t->host, "GET", service, 0, 1)) < 0) {- giterr_set(GITERR_NET, "Failed to generate request");- goto cleanup;- }-- if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0)- goto cleanup;-- ret = store_refs(t);--cleanup:- git_buf_free(&request);- git_buf_clear(&t->buf);-- return ret;-}--static int http_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque)-{- transport_http *t = (transport_http *) transport;- git_vector *refs = &t->refs;- unsigned int i;- git_pkt_ref *p;-- git_vector_foreach(refs, i, p) {- if (p->type != GIT_PKT_REF)- continue;-- if (list_cb(&p->head, opaque) < 0) {- giterr_set(GITERR_NET, "The user callback returned error");- return -1;- }- }-- return 0;-}--static int on_body_parse_response(http_parser *parser, const char *str, size_t len)-{- transport_http *t = (transport_http *) parser->data;- git_buf *buf = &t->buf;- git_vector *common = &t->common;- int error;- const char *line_end, *ptr;-- if (len == 0) { /* EOF */- if (git_buf_len(buf) != 0) {- giterr_set(GITERR_NET, "Unexpected EOF");- return t->error = -1;- } else {- return 0;- }- }-- git_buf_put(buf, str, len);- ptr = buf->ptr;- while (1) {- git_pkt *pkt;-- if (git_buf_len(buf) == 0)- return 0;-- error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf));- if (error == GIT_EBUFS) {- return 0; /* Ask for more */- }- if (error < 0)- return t->error = -1;-- git_buf_consume(buf, line_end);-- if (pkt->type == GIT_PKT_PACK) {- git__free(pkt);- t->pack_ready = 1;- return 0;- }-- if (pkt->type == GIT_PKT_NAK) {- git__free(pkt);- return 0;- }-- if (pkt->type != GIT_PKT_ACK) {- git__free(pkt);- continue;- }-- if (git_vector_insert(common, pkt) < 0)- return -1;- }-- return error;--}--static int parse_response(transport_http *t)-{- int ret = 0;- http_parser_settings settings;- char buffer[1024];- gitno_buffer buf;-- http_parser_init(&t->parser, HTTP_RESPONSE);- t->parser.data = t;- t->transfer_finished = 0;- memset(&settings, 0x0, sizeof(http_parser_settings));- settings.on_header_field = on_header_field;- settings.on_header_value = on_header_value;- settings.on_headers_complete = on_headers_complete;- settings.on_body = on_body_parse_response;- settings.on_message_complete = on_message_complete;-- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);-- while(1) {- size_t parsed;-- if ((ret = gitno_recv(&buf)) < 0)- return -1;-- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);- /* Both should happen at the same time */- if (parsed != buf.offset || t->error < 0)- return t->error;-- gitno_consume_n(&buf, parsed);-- if (ret == 0 || t->transfer_finished || t->pack_ready) {- return 0;- }- }-- return ret;-}--static int http_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)-{- transport_http *t = (transport_http *) transport;- int ret;- unsigned int i;- char buff[128];- gitno_buffer buf;- git_revwalk *walk = NULL;- git_oid oid;- git_pkt_ack *pkt;- git_vector *common = &t->common;- const char *prefix = "http://", *url = t->parent.url;- git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT;- gitno_buffer_setup(&buf, buff, sizeof(buff), t->socket);-- /* TODO: Store url in the transport */- if (!git__prefixcmp(url, prefix))- url += strlen(prefix);-- if (git_vector_init(common, 16, NULL) < 0)- return -1;-- if (git_fetch_setup_walk(&walk, repo) < 0)- return -1;-- do {- if ((ret = do_connect(t, t->host, t->port)) < 0)- goto cleanup;-- if ((ret = git_pkt_buffer_wants(wants, &t->caps, &data)) < 0)- goto cleanup;-- /* We need to send these on each connection */- git_vector_foreach (common, i, pkt) {- if ((ret = git_pkt_buffer_have(&pkt->oid, &data)) < 0)- goto cleanup;- }-- i = 0;- while ((i < 20) && ((ret = git_revwalk_next(&oid, walk)) == 0)) {- if ((ret = git_pkt_buffer_have(&oid, &data)) < 0)- goto cleanup;-- i++;- }-- git_pkt_buffer_done(&data);-- if ((ret = gen_request(&request, url, t->host, "POST", "upload-pack", data.size, 0)) < 0)- goto cleanup;-- if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0)- goto cleanup;-- if ((ret = gitno_send(t->socket, data.ptr, data.size, 0)) < 0)- goto cleanup;-- git_buf_clear(&request);- git_buf_clear(&data);-- if (ret < 0 || i >= 256)- break;-- if ((ret = parse_response(t)) < 0)- goto cleanup;-- if (t->pack_ready) {- ret = 0;- goto cleanup;- }-- } while(1);--cleanup:- git_buf_free(&request);- git_buf_free(&data);- git_revwalk_free(walk);- return ret;-}--typedef struct {- git_indexer_stream *idx;- git_indexer_stats *stats;- transport_http *transport;-} download_pack_cbdata;--static int on_message_complete_download_pack(http_parser *parser)-{- download_pack_cbdata *data = (download_pack_cbdata *) parser->data;-- data->transport->transfer_finished = 1;-- return 0;-}-static int on_body_download_pack(http_parser *parser, const char *str, size_t len)-{- download_pack_cbdata *data = (download_pack_cbdata *) parser->data;- transport_http *t = data->transport;- git_indexer_stream *idx = data->idx;- git_indexer_stats *stats = data->stats;-- return t->error = git_indexer_stream_add(idx, str, len, stats);-}--/*- * As the server is probably using Transfer-Encoding: chunked, we have- * to use the HTTP parser to download the pack instead of giving it to- * the simple downloader. Furthermore, we're using keep-alive- * connections, so the simple downloader would just hang.- */-static int http_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats)-{- transport_http *t = (transport_http *) transport;- git_buf *oldbuf = &t->buf;- int recvd;- http_parser_settings settings;- char buffer[1024];- gitno_buffer buf;- git_indexer_stream *idx = NULL;- download_pack_cbdata data;-- gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket);-- if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) {- giterr_set(GITERR_NET, "The pack doesn't start with a pack signature");- return -1;- }-- if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0)- return -1;--- /*- * This is part of the previous response, so we don't want to- * re-init the parser, just set these two callbacks.- */- memset(stats, 0, sizeof(git_indexer_stats));- data.stats = stats;- data.idx = idx;- data.transport = t;- t->parser.data = &data;- t->transfer_finished = 0;- memset(&settings, 0x0, sizeof(settings));- settings.on_message_complete = on_message_complete_download_pack;- settings.on_body = on_body_download_pack;- *bytes = git_buf_len(oldbuf);-- if (git_indexer_stream_add(idx, git_buf_cstr(oldbuf), git_buf_len(oldbuf), stats) < 0)- goto on_error;-- do {- size_t parsed;-- if ((recvd = gitno_recv(&buf)) < 0)- goto on_error;-- parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset);- if (parsed != buf.offset || t->error < 0)- goto on_error;-- *bytes += recvd;- gitno_consume_n(&buf, parsed);- } while (recvd > 0 && !t->transfer_finished);-- if (git_indexer_stream_finalize(idx, stats) < 0)- goto on_error;-- git_indexer_stream_free(idx);- return 0;--on_error:- git_indexer_stream_free(idx);- return -1;-}--static int http_close(git_transport *transport)-{- transport_http *t = (transport_http *) transport;-- if (gitno_close(t->socket) < 0) {- giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno));- return -1;- }-- return 0;-}---static void http_free(git_transport *transport)-{- transport_http *t = (transport_http *) transport;- git_vector *refs = &t->refs;- git_vector *common = &t->common;- unsigned int i;- git_pkt *p;--#ifdef GIT_WIN32- /* cleanup the WSA context. note that this context- * can be initialized more than once with WSAStartup(),- * and needs to be cleaned one time for each init call- */- WSACleanup();-#endif-- git_vector_foreach(refs, i, p) {- git_pkt_free(p);- }- git_vector_free(refs);- git_vector_foreach(common, i, p) {- git_pkt_free(p);- }- git_vector_free(common);- git_buf_free(&t->buf);- git_buf_free(&t->proto.buf);- git__free(t->heads);- git__free(t->content_type);- git__free(t->host);- git__free(t->port);- git__free(t->service);- git__free(t->parent.url);- git__free(t);-}--int git_transport_http(git_transport **out)-{- transport_http *t;-- t = git__malloc(sizeof(transport_http));- GITERR_CHECK_ALLOC(t);-- memset(t, 0x0, sizeof(transport_http));-- t->parent.connect = http_connect;- t->parent.ls = http_ls;- t->parent.negotiate_fetch = http_negotiate_fetch;- t->parent.download_pack = http_download_pack;- t->parent.close = http_close;- t->parent.free = http_free;- t->proto.refs = &t->refs;- t->proto.transport = (git_transport *) t;--#ifdef GIT_WIN32- /* on win32, the WSA context needs to be initialized- * before any socket calls can be performed */- if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {- http_free((git_transport *) t);- giterr_set(GITERR_OS, "Winsock init failed");- return -1;- }-#endif-- *out = (git_transport *) t;- return 0;-}+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef GIT_WINHTTP++#include "git2.h"+#include "http_parser.h"+#include "buffer.h"+#include "netops.h"+#include "smart.h"++static const char *prefix_http = "http://";+static const char *prefix_https = "https://";+static const char *upload_pack_service = "upload-pack";+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";+static const char *upload_pack_service_url = "/git-upload-pack";+static const char *receive_pack_service = "receive-pack";+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";+static const char *receive_pack_service_url = "/git-receive-pack";+static const char *get_verb = "GET";+static const char *post_verb = "POST";+static const char *basic_authtype = "Basic";++#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)++#define PARSE_ERROR_GENERIC -1+#define PARSE_ERROR_REPLAY -2++#define CHUNK_SIZE 4096++enum last_cb {+ NONE,+ FIELD,+ VALUE+};++typedef enum {+ GIT_HTTP_AUTH_BASIC = 1,+} http_authmechanism_t;++typedef struct {+ git_smart_subtransport_stream parent;+ const char *service;+ const char *service_url;+ char *redirect_url;+ const char *verb;+ char *chunk_buffer;+ unsigned chunk_buffer_len;+ unsigned sent_request : 1,+ received_response : 1,+ chunked : 1,+ redirect_count : 3;+} http_stream;++typedef struct {+ git_smart_subtransport parent;+ transport_smart *owner;+ gitno_socket socket;+ const char *path;+ char *host;+ char *port;+ char *user_from_url;+ char *pass_from_url;+ git_cred *cred;+ git_cred *url_cred;+ http_authmechanism_t auth_mechanism;+ unsigned connected : 1,+ use_ssl : 1;++ /* Parser structures */+ http_parser parser;+ http_parser_settings settings;+ gitno_buffer parse_buffer;+ git_buf parse_header_name;+ git_buf parse_header_value;+ char parse_buffer_data[2048];+ char *content_type;+ char *location;+ git_vector www_authenticate;+ enum last_cb last_cb;+ int parse_error;+ unsigned parse_finished : 1;+} http_subtransport;++typedef struct {+ http_stream *s;+ http_subtransport *t;++ /* Target buffer details from read() */+ char *buffer;+ size_t buf_size;+ size_t *bytes_read;+} parser_context;++static int apply_basic_credential(git_buf *buf, git_cred *cred)+{+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;+ git_buf raw = GIT_BUF_INIT;+ int error = -1;++ git_buf_printf(&raw, "%s:%s", c->username, c->password);++ if (git_buf_oom(&raw) ||+ git_buf_puts(buf, "Authorization: Basic ") < 0 ||+ git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 ||+ git_buf_puts(buf, "\r\n") < 0)+ goto on_error;++ error = 0;++on_error:+ if (raw.size)+ memset(raw.ptr, 0x0, raw.size);++ git_buf_free(&raw);+ return error;+}++static int gen_request(+ git_buf *buf,+ http_stream *s,+ size_t content_length)+{+ http_subtransport *t = OWNING_SUBTRANSPORT(s);++ if (!t->path)+ t->path = "/";++ /* If we were redirected, make sure to respect that here */+ if (s->redirect_url)+ git_buf_printf(buf, "%s %s HTTP/1.1\r\n", s->verb, s->redirect_url);+ else+ git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, t->path, s->service_url);++ git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");+ git_buf_printf(buf, "Host: %s\r\n", t->host);++ if (s->chunked || content_length > 0) {+ git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service);+ git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service);++ if (s->chunked)+ git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");+ else+ git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);+ } else+ git_buf_puts(buf, "Accept: */*\r\n");++ /* Apply credentials to the request */+ if (t->cred && t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&+ t->auth_mechanism == GIT_HTTP_AUTH_BASIC &&+ apply_basic_credential(buf, t->cred) < 0)+ return -1;++ /* Use url-parsed basic auth if username and password are both provided */+ if (!t->cred && t->user_from_url && t->pass_from_url) {+ if (!t->url_cred &&+ git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0)+ return -1;+ if (apply_basic_credential(buf, t->url_cred) < 0) return -1;+ }++ git_buf_puts(buf, "\r\n");++ if (git_buf_oom(buf))+ return -1;++ return 0;+}++static int parse_unauthorized_response(+ git_vector *www_authenticate,+ int *allowed_types,+ http_authmechanism_t *auth_mechanism)+{+ unsigned i;+ char *entry;++ git_vector_foreach(www_authenticate, i, entry) {+ if (!strncmp(entry, basic_authtype, 5) &&+ (entry[5] == '\0' || entry[5] == ' ')) {+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;+ *auth_mechanism = GIT_HTTP_AUTH_BASIC;+ }+ }++ return 0;+}++static int on_header_ready(http_subtransport *t)+{+ git_buf *name = &t->parse_header_name;+ git_buf *value = &t->parse_header_value;++ if (!strcasecmp("Content-Type", git_buf_cstr(name))) {+ if (!t->content_type) {+ t->content_type = git__strdup(git_buf_cstr(value));+ GITERR_CHECK_ALLOC(t->content_type);+ }+ }+ else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {+ char *dup = git__strdup(git_buf_cstr(value));+ GITERR_CHECK_ALLOC(dup);++ git_vector_insert(&t->www_authenticate, dup);+ }+ else if (!strcasecmp("Location", git_buf_cstr(name))) {+ if (!t->location) {+ t->location= git__strdup(git_buf_cstr(value));+ GITERR_CHECK_ALLOC(t->location);+ }+ }++ return 0;+}++static int on_header_field(http_parser *parser, const char *str, size_t len)+{+ parser_context *ctx = (parser_context *) parser->data;+ http_subtransport *t = ctx->t;++ /* Both parse_header_name and parse_header_value are populated+ * and ready for consumption */+ if (VALUE == t->last_cb)+ if (on_header_ready(t) < 0)+ return t->parse_error = PARSE_ERROR_GENERIC;++ if (NONE == t->last_cb || VALUE == t->last_cb)+ git_buf_clear(&t->parse_header_name);++ if (git_buf_put(&t->parse_header_name, str, len) < 0)+ return t->parse_error = PARSE_ERROR_GENERIC;++ t->last_cb = FIELD;+ return 0;+}++static int on_header_value(http_parser *parser, const char *str, size_t len)+{+ parser_context *ctx = (parser_context *) parser->data;+ http_subtransport *t = ctx->t;++ assert(NONE != t->last_cb);++ if (FIELD == t->last_cb)+ git_buf_clear(&t->parse_header_value);++ if (git_buf_put(&t->parse_header_value, str, len) < 0)+ return t->parse_error = PARSE_ERROR_GENERIC;++ t->last_cb = VALUE;+ return 0;+}++static int on_headers_complete(http_parser *parser)+{+ parser_context *ctx = (parser_context *) parser->data;+ http_subtransport *t = ctx->t;+ http_stream *s = ctx->s;+ git_buf buf = GIT_BUF_INIT;++ /* Both parse_header_name and parse_header_value are populated+ * and ready for consumption. */+ if (VALUE == t->last_cb)+ if (on_header_ready(t) < 0)+ return t->parse_error = PARSE_ERROR_GENERIC;++ /* Check for an authentication failure. */+ if (parser->status_code == 401 &&+ get_verb == s->verb &&+ t->owner->cred_acquire_cb) {+ int allowed_types = 0;++ if (parse_unauthorized_response(&t->www_authenticate,+ &allowed_types, &t->auth_mechanism) < 0)+ return t->parse_error = PARSE_ERROR_GENERIC;++ if (allowed_types &&+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {++ if (t->owner->cred_acquire_cb(&t->cred,+ t->owner->url,+ t->user_from_url,+ allowed_types,+ t->owner->cred_acquire_payload) < 0)+ return PARSE_ERROR_GENERIC;++ assert(t->cred);++ /* Successfully acquired a credential. */+ return t->parse_error = PARSE_ERROR_REPLAY;+ }+ }++ /* Check for a redirect.+ * Right now we only permit a redirect to the same hostname. */+ if ((parser->status_code == 301 ||+ parser->status_code == 302 ||+ (parser->status_code == 303 && get_verb == s->verb) ||+ parser->status_code == 307) &&+ t->location) {++ if (s->redirect_count >= 7) {+ giterr_set(GITERR_NET, "Too many redirects");+ return t->parse_error = PARSE_ERROR_GENERIC;+ }++ if (t->location[0] != '/') {+ giterr_set(GITERR_NET, "Only relative redirects are supported");+ return t->parse_error = PARSE_ERROR_GENERIC;+ }++ /* Set the redirect URL on the stream. This is a transfer of+ * ownership of the memory. */+ if (s->redirect_url)+ git__free(s->redirect_url);++ s->redirect_url = t->location;+ t->location = NULL;++ t->connected = 0;+ s->redirect_count++;++ return t->parse_error = PARSE_ERROR_REPLAY;+ }++ /* Check for a 200 HTTP status code. */+ if (parser->status_code != 200) {+ giterr_set(GITERR_NET,+ "Unexpected HTTP status code: %d",+ parser->status_code);+ return t->parse_error = PARSE_ERROR_GENERIC;+ }++ /* The response must contain a Content-Type header. */+ if (!t->content_type) {+ giterr_set(GITERR_NET, "No Content-Type header in response");+ return t->parse_error = PARSE_ERROR_GENERIC;+ }++ /* The Content-Type header must match our expectation. */+ if (get_verb == s->verb)+ git_buf_printf(&buf,+ "application/x-git-%s-advertisement",+ ctx->s->service);+ else+ git_buf_printf(&buf,+ "application/x-git-%s-result",+ ctx->s->service);++ if (git_buf_oom(&buf))+ return t->parse_error = PARSE_ERROR_GENERIC;++ if (strcmp(t->content_type, git_buf_cstr(&buf))) {+ git_buf_free(&buf);+ giterr_set(GITERR_NET,+ "Invalid Content-Type: %s",+ t->content_type);+ return t->parse_error = PARSE_ERROR_GENERIC;+ }++ git_buf_free(&buf);++ return 0;+}++static int on_message_complete(http_parser *parser)+{+ parser_context *ctx = (parser_context *) parser->data;+ http_subtransport *t = ctx->t;++ t->parse_finished = 1;++ return 0;+}++static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)+{+ parser_context *ctx = (parser_context *) parser->data;+ http_subtransport *t = ctx->t;++ if (ctx->buf_size < len) {+ giterr_set(GITERR_NET, "Can't fit data in the buffer");+ return t->parse_error = PARSE_ERROR_GENERIC;+ }++ memcpy(ctx->buffer, str, len);+ *(ctx->bytes_read) += len;+ ctx->buffer += len;+ ctx->buf_size -= len;++ return 0;+}++static void clear_parser_state(http_subtransport *t)+{+ unsigned i;+ char *entry;++ http_parser_init(&t->parser, HTTP_RESPONSE);+ gitno_buffer_setup(&t->socket,+ &t->parse_buffer,+ t->parse_buffer_data,+ sizeof(t->parse_buffer_data));++ t->last_cb = NONE;+ t->parse_error = 0;+ t->parse_finished = 0;++ git_buf_free(&t->parse_header_name);+ git_buf_init(&t->parse_header_name, 0);++ git_buf_free(&t->parse_header_value);+ git_buf_init(&t->parse_header_value, 0);++ git__free(t->content_type);+ t->content_type = NULL;++ git__free(t->location);+ t->location = NULL;++ git_vector_foreach(&t->www_authenticate, i, entry)+ git__free(entry);++ git_vector_free(&t->www_authenticate);+}++static int write_chunk(gitno_socket *socket, const char *buffer, size_t len)+{+ git_buf buf = GIT_BUF_INIT;++ /* Chunk header */+ git_buf_printf(&buf, "%X\r\n", (unsigned)len);++ if (git_buf_oom(&buf))+ return -1;++ if (gitno_send(socket, buf.ptr, buf.size, 0) < 0) {+ git_buf_free(&buf);+ return -1;+ }++ git_buf_free(&buf);++ /* Chunk body */+ if (len > 0 && gitno_send(socket, buffer, len, 0) < 0)+ return -1;++ /* Chunk footer */+ if (gitno_send(socket, "\r\n", 2, 0) < 0)+ return -1;++ return 0;+}++static int http_connect(http_subtransport *t)+{+ int flags = 0;++ if (t->connected &&+ http_should_keep_alive(&t->parser) &&+ http_body_is_final(&t->parser))+ return 0;++ if (t->socket.socket)+ gitno_close(&t->socket);++ if (t->use_ssl) {+ int tflags;++ if (t->owner->parent.read_flags(&t->owner->parent, &tflags) < 0)+ return -1;++ flags |= GITNO_CONNECT_SSL;++ if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & tflags)+ flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;+ }++ if (gitno_connect(&t->socket, t->host, t->port, flags) < 0)+ return -1;++ t->connected = 1;+ return 0;+}++static int http_stream_read(+ git_smart_subtransport_stream *stream,+ char *buffer,+ size_t buf_size,+ size_t *bytes_read)+{+ http_stream *s = (http_stream *)stream;+ http_subtransport *t = OWNING_SUBTRANSPORT(s);+ parser_context ctx;+ size_t bytes_parsed;++replay:+ *bytes_read = 0;++ assert(t->connected);++ if (!s->sent_request) {+ git_buf request = GIT_BUF_INIT;++ clear_parser_state(t);++ if (gen_request(&request, s, 0) < 0) {+ giterr_set(GITERR_NET, "Failed to generate request");+ return -1;+ }++ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {+ git_buf_free(&request);+ return -1;+ }++ git_buf_free(&request);++ s->sent_request = 1;+ }++ if (!s->received_response) {+ if (s->chunked) {+ assert(s->verb == post_verb);++ /* Flush, if necessary */+ if (s->chunk_buffer_len > 0 &&+ write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)+ return -1;++ s->chunk_buffer_len = 0;++ /* Write the final chunk. */+ if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0)+ return -1;+ }++ s->received_response = 1;+ }++ while (!*bytes_read && !t->parse_finished) {+ t->parse_buffer.offset = 0;++ if (gitno_recv(&t->parse_buffer) < 0)+ return -1;++ /* This call to http_parser_execute will result in invocations of the+ * on_* family of callbacks. The most interesting of these is+ * on_body_fill_buffer, which is called when data is ready to be copied+ * into the target buffer. We need to marshal the buffer, buf_size, and+ * bytes_read parameters to this callback. */+ ctx.t = t;+ ctx.s = s;+ ctx.buffer = buffer;+ ctx.buf_size = buf_size;+ ctx.bytes_read = bytes_read;++ /* Set the context, call the parser, then unset the context. */+ t->parser.data = &ctx;++ bytes_parsed = http_parser_execute(&t->parser,+ &t->settings,+ t->parse_buffer.data,+ t->parse_buffer.offset);++ t->parser.data = NULL;++ /* If there was a handled authentication failure, then parse_error+ * will have signaled us that we should replay the request. */+ if (PARSE_ERROR_REPLAY == t->parse_error) {+ s->sent_request = 0;++ if (http_connect(t) < 0)+ return -1;++ goto replay;+ }++ if (t->parse_error < 0)+ return -1;++ if (bytes_parsed != t->parse_buffer.offset) {+ giterr_set(GITERR_NET,+ "HTTP parser error: %s",+ http_errno_description((enum http_errno)t->parser.http_errno));+ return -1;+ }+ }++ return 0;+}++static int http_stream_write_chunked(+ git_smart_subtransport_stream *stream,+ const char *buffer,+ size_t len)+{+ http_stream *s = (http_stream *)stream;+ http_subtransport *t = OWNING_SUBTRANSPORT(s);++ assert(t->connected);++ /* Send the request, if necessary */+ if (!s->sent_request) {+ git_buf request = GIT_BUF_INIT;++ clear_parser_state(t);++ if (gen_request(&request, s, 0) < 0) {+ giterr_set(GITERR_NET, "Failed to generate request");+ return -1;+ }++ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {+ git_buf_free(&request);+ return -1;+ }++ git_buf_free(&request);++ s->sent_request = 1;+ }++ if (len > CHUNK_SIZE) {+ /* Flush, if necessary */+ if (s->chunk_buffer_len > 0) {+ if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)+ return -1;++ s->chunk_buffer_len = 0;+ }++ /* Write chunk directly */+ if (write_chunk(&t->socket, buffer, len) < 0)+ return -1;+ }+ else {+ /* Append as much to the buffer as we can */+ int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);++ if (!s->chunk_buffer)+ s->chunk_buffer = git__malloc(CHUNK_SIZE);++ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);+ s->chunk_buffer_len += count;+ buffer += count;+ len -= count;++ /* Is the buffer full? If so, then flush */+ if (CHUNK_SIZE == s->chunk_buffer_len) {+ if (write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)+ return -1;++ s->chunk_buffer_len = 0;++ if (len > 0) {+ memcpy(s->chunk_buffer, buffer, len);+ s->chunk_buffer_len = len;+ }+ }+ }++ return 0;+}++static int http_stream_write_single(+ git_smart_subtransport_stream *stream,+ const char *buffer,+ size_t len)+{+ http_stream *s = (http_stream *)stream;+ http_subtransport *t = OWNING_SUBTRANSPORT(s);+ git_buf request = GIT_BUF_INIT;++ assert(t->connected);++ if (s->sent_request) {+ giterr_set(GITERR_NET, "Subtransport configured for only one write");+ return -1;+ }++ clear_parser_state(t);++ if (gen_request(&request, s, len) < 0) {+ giterr_set(GITERR_NET, "Failed to generate request");+ return -1;+ }++ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0)+ goto on_error;++ if (len && gitno_send(&t->socket, buffer, len, 0) < 0)+ goto on_error;++ git_buf_free(&request);+ s->sent_request = 1;++ return 0;++on_error:+ git_buf_free(&request);+ return -1;+}++static void http_stream_free(git_smart_subtransport_stream *stream)+{+ http_stream *s = (http_stream *)stream;++ if (s->chunk_buffer)+ git__free(s->chunk_buffer);++ if (s->redirect_url)+ git__free(s->redirect_url);++ git__free(s);+}++static int http_stream_alloc(http_subtransport *t,+ git_smart_subtransport_stream **stream)+{+ http_stream *s;++ if (!stream)+ return -1;++ s = git__calloc(sizeof(http_stream), 1);+ GITERR_CHECK_ALLOC(s);++ s->parent.subtransport = &t->parent;+ s->parent.read = http_stream_read;+ s->parent.write = http_stream_write_single;+ s->parent.free = http_stream_free;++ *stream = (git_smart_subtransport_stream *)s;+ return 0;+}++static int http_uploadpack_ls(+ http_subtransport *t,+ git_smart_subtransport_stream **stream)+{+ http_stream *s;++ if (http_stream_alloc(t, stream) < 0)+ return -1;++ s = (http_stream *)*stream;++ s->service = upload_pack_service;+ s->service_url = upload_pack_ls_service_url;+ s->verb = get_verb;++ return 0;+}++static int http_uploadpack(+ http_subtransport *t,+ git_smart_subtransport_stream **stream)+{+ http_stream *s;++ if (http_stream_alloc(t, stream) < 0)+ return -1;++ s = (http_stream *)*stream;++ s->service = upload_pack_service;+ s->service_url = upload_pack_service_url;+ s->verb = post_verb;++ return 0;+}++static int http_receivepack_ls(+ http_subtransport *t,+ git_smart_subtransport_stream **stream)+{+ http_stream *s;++ if (http_stream_alloc(t, stream) < 0)+ return -1;++ s = (http_stream *)*stream;++ s->service = receive_pack_service;+ s->service_url = receive_pack_ls_service_url;+ s->verb = get_verb;++ return 0;+}++static int http_receivepack(+ http_subtransport *t,+ git_smart_subtransport_stream **stream)+{+ http_stream *s;++ if (http_stream_alloc(t, stream) < 0)+ return -1;++ s = (http_stream *)*stream;++ /* Use Transfer-Encoding: chunked for this request */+ s->chunked = 1;+ s->parent.write = http_stream_write_chunked;++ s->service = receive_pack_service;+ s->service_url = receive_pack_service_url;+ s->verb = post_verb;++ return 0;+}++static int http_action(+ git_smart_subtransport_stream **stream,+ git_smart_subtransport *subtransport,+ const char *url,+ git_smart_service_t action)+{+ http_subtransport *t = (http_subtransport *)subtransport;+ const char *default_port = NULL;+ int ret;++ if (!stream)+ return -1;++ if (!t->host || !t->port || !t->path) {+ if (!git__prefixcmp(url, prefix_http)) {+ url = url + strlen(prefix_http);+ default_port = "80";+ }++ if (!git__prefixcmp(url, prefix_https)) {+ url += strlen(prefix_https);+ default_port = "443";+ t->use_ssl = 1;+ }++ if (!default_port)+ return -1;++ if ((ret = gitno_extract_url_parts(&t->host, &t->port,+ &t->user_from_url, &t->pass_from_url, url, default_port)) < 0)+ return ret;++ t->path = strchr(url, '/');+ }++ if (http_connect(t) < 0)+ return -1;++ switch (action)+ {+ case GIT_SERVICE_UPLOADPACK_LS:+ return http_uploadpack_ls(t, stream);++ case GIT_SERVICE_UPLOADPACK:+ return http_uploadpack(t, stream);++ case GIT_SERVICE_RECEIVEPACK_LS:+ return http_receivepack_ls(t, stream);++ case GIT_SERVICE_RECEIVEPACK:+ return http_receivepack(t, stream);+ }++ *stream = NULL;+ return -1;+}++static int http_close(git_smart_subtransport *subtransport)+{+ http_subtransport *t = (http_subtransport *) subtransport;++ clear_parser_state(t);++ if (t->socket.socket) {+ gitno_close(&t->socket);+ memset(&t->socket, 0x0, sizeof(gitno_socket));+ }++ if (t->cred) {+ t->cred->free(t->cred);+ t->cred = NULL;+ }++ if (t->url_cred) {+ t->url_cred->free(t->url_cred);+ t->url_cred = NULL;+ }++ if (t->host) {+ git__free(t->host);+ t->host = NULL;+ }++ if (t->port) {+ git__free(t->port);+ t->port = NULL;+ }++ if (t->user_from_url) {+ git__free(t->user_from_url);+ t->user_from_url = NULL;+ }++ if (t->pass_from_url) {+ git__free(t->pass_from_url);+ t->pass_from_url = NULL;+ }++ return 0;+}++static void http_free(git_smart_subtransport *subtransport)+{+ http_subtransport *t = (http_subtransport *) subtransport;++ http_close(subtransport);++ git__free(t);+}++int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)+{+ http_subtransport *t;++ if (!out)+ return -1;++ t = git__calloc(sizeof(http_subtransport), 1);+ GITERR_CHECK_ALLOC(t);++ t->owner = (transport_smart *)owner;+ t->parent.action = http_action;+ t->parent.close = http_close;+ t->parent.free = http_free;++ t->settings.on_header_field = on_header_field;+ t->settings.on_header_value = on_header_value;+ t->settings.on_headers_complete = on_headers_complete;+ t->settings.on_body = on_body_fill_buffer;+ t->settings.on_message_complete = on_message_complete;++ *out = (git_smart_subtransport *) t;+ return 0;+}++#endif /* !GIT_WINHTTP */
libgit2/src/transports/local.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,16 +10,34 @@ #include "git2/repository.h" #include "git2/object.h" #include "git2/tag.h"+#include "git2/transport.h"+#include "git2/revwalk.h"+#include "git2/odb_backend.h"+#include "git2/pack.h"+#include "git2/commit.h"+#include "git2/revparse.h"+#include "git2/push.h"+#include "pack-objects.h" #include "refs.h"-#include "transport.h" #include "posix.h" #include "path.h" #include "buffer.h"+#include "repository.h"+#include "odb.h"+#include "push.h"+#include "remote.h" typedef struct { git_transport parent;+ git_remote *owner;+ char *url;+ int direction;+ int flags;+ git_atomic cancelled; git_repository *repo; git_vector refs;+ unsigned connected : 1,+ have_refs : 1; } transport_local; static int add_ref(transport_local *t, const char *name)@@ -28,15 +46,28 @@ git_remote_head *head; git_object *obj = NULL, *target = NULL; git_buf buf = GIT_BUF_INIT;+ int error; - head = git__malloc(sizeof(git_remote_head));+ head = git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); head->name = git__strdup(name); GITERR_CHECK_ALLOC(head->name); - if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0 ||- git_vector_insert(&t->refs, head) < 0)+ error = git_reference_name_to_id(&head->oid, t->repo, name);+ if (error < 0) {+ git__free(head->name);+ git__free(head);+ if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {+ /* This is actually okay. Empty repos often have a HEAD that points to+ * a nonexistent "refs/heads/master". */+ giterr_clear();+ return 0;+ }+ return error;+ }++ if (git_vector_insert(&t->refs, head) < 0) { git__free(head->name); git__free(head);@@ -52,14 +83,16 @@ head = NULL; - /* If it's not an annotated tag, just get out */- if (git_object_type(obj) != GIT_OBJ_TAG) {+ /* If it's not an annotated tag, or if we're mocking+ * git-receive-pack, just get out */+ if (git_object_type(obj) != GIT_OBJ_TAG ||+ t->direction != GIT_DIRECTION_FETCH) { git_object_free(obj); return 0; } /* And if it's a tag, peel it, and add it to the list */- head = git__malloc(sizeof(git_remote_head));+ head = git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); if (git_buf_join(&buf, 0, name, peeled) < 0) return -1;@@ -92,14 +125,14 @@ assert(t); if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||- git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)+ git_vector_init(&t->refs, ref_names.count, NULL) < 0) goto on_error; /* Sort the references first */ git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); - /* Add HEAD */- if (add_ref(t, GIT_HEAD_FILE) < 0)+ /* Add HEAD iff direction is fetch */+ if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0) goto on_error; for (i = 0; i < ref_names.count; ++i) {@@ -107,6 +140,7 @@ goto on_error; } + t->have_refs = 1; git_strarray_free(&ref_names); return 0; @@ -116,28 +150,16 @@ return -1; } -static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)-{- transport_local *t = (transport_local *) transport;- git_vector *refs = &t->refs;- unsigned int i;- git_remote_head *h;-- assert(transport && transport->connected);-- git_vector_foreach(refs, i, h) {- if (list_cb(h, payload) < 0)- return -1;- }-- return 0;-}- /* * Try to open the url as a git directory. The direction doesn't * matter in this case because we're calulating the heads ourselves. */-static int local_connect(git_transport *transport, int direction)+static int local_connect(+ git_transport *transport,+ const char *url,+ git_cred_acquire_cb cred_acquire_cb,+ void *cred_acquire_payload,+ int direction, int flags) { git_repository *repo; int error;@@ -145,18 +167,24 @@ const char *path; git_buf buf = GIT_BUF_INIT; - GIT_UNUSED(direction);+ GIT_UNUSED(cred_acquire_cb);+ GIT_UNUSED(cred_acquire_payload); + t->url = git__strdup(url);+ GITERR_CHECK_ALLOC(t->url);+ t->direction = direction;+ t->flags = flags;+ /* The repo layer doesn't want the prefix */- if (!git__prefixcmp(transport->url, "file://")) {- if (git_path_fromurl(&buf, transport->url) < 0) {+ if (!git__prefixcmp(t->url, "file://")) {+ if (git_path_fromurl(&buf, t->url) < 0) { git_buf_free(&buf); return -1; } path = git_buf_cstr(&buf); } else { /* We assume transport->url is already a path */- path = transport->url;+ path = t->url; } error = git_repository_open(&repo, path);@@ -171,47 +199,411 @@ if (store_refs(t) < 0) return -1; - t->parent.connected = 1;+ t->connected = 1; return 0; } -static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)+static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload) {- GIT_UNUSED(transport);- GIT_UNUSED(repo);- GIT_UNUSED(wants);+ transport_local *t = (transport_local *)transport;+ unsigned int i;+ git_remote_head *head = NULL; - giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry");- return -1;+ if (!t->have_refs) {+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");+ return -1;+ }++ git_vector_foreach(&t->refs, i, head) {+ if (list_cb(head, payload))+ return GIT_EUSER;+ }++ return 0; } +static int local_negotiate_fetch(+ git_transport *transport,+ git_repository *repo,+ const git_remote_head * const *refs,+ size_t count)+{+ transport_local *t = (transport_local*)transport;+ git_remote_head *rhead;+ unsigned int i;++ GIT_UNUSED(refs);+ GIT_UNUSED(count);++ /* Fill in the loids */+ git_vector_foreach(&t->refs, i, rhead) {+ git_object *obj;++ int error = git_revparse_single(&obj, repo, rhead->name);+ if (!error)+ git_oid_cpy(&rhead->loid, git_object_id(obj));+ else if (error != GIT_ENOTFOUND)+ return error;+ git_object_free(obj);+ giterr_clear();+ }++ return 0;+}++static int local_push_copy_object(+ git_odb *local_odb,+ git_odb *remote_odb,+ git_pobject *obj)+{+ int error = 0;+ git_odb_object *odb_obj = NULL;+ git_odb_stream *odb_stream;+ size_t odb_obj_size;+ git_otype odb_obj_type;+ git_oid remote_odb_obj_oid;++ /* Object already exists in the remote ODB; do nothing and return 0*/+ if (git_odb_exists(remote_odb, &obj->id))+ return 0;++ if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0)+ return error;++ odb_obj_size = git_odb_object_size(odb_obj);+ odb_obj_type = git_odb_object_type(odb_obj);++ if ((error = git_odb_open_wstream(&odb_stream, remote_odb,+ odb_obj_size, odb_obj_type)) < 0)+ goto on_error;++ if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj),+ odb_obj_size) < 0 ||+ odb_stream->finalize_write(&remote_odb_obj_oid, odb_stream) < 0) {+ error = -1;+ } else if (git_oid_cmp(&obj->id, &remote_odb_obj_oid) != 0) {+ giterr_set(GITERR_ODB, "Error when writing object to remote odb "+ "during local push operation. Remote odb object oid does not "+ "match local oid.");+ error = -1;+ }++ odb_stream->free(odb_stream);++on_error:+ git_odb_object_free(odb_obj);+ return error;+}++static int local_push_update_remote_ref(+ git_repository *remote_repo,+ const char *lref,+ const char *rref,+ git_oid *loid,+ git_oid *roid)+{+ int error;+ git_reference *remote_ref = NULL;++ /* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */+ rref = rref ? rref : lref;++ if (lref) {+ /* Create or update a ref */+ if ((error = git_reference_create(NULL, remote_repo, rref, loid,+ !git_oid_iszero(roid))) < 0)+ return error;+ } else {+ /* Delete a ref */+ if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {+ if (error == GIT_ENOTFOUND)+ error = 0;+ return error;+ }++ if ((error = git_reference_delete(remote_ref)) < 0)+ return error;++ git_reference_free(remote_ref);+ }++ return 0;+}++static int local_push(+ git_transport *transport,+ git_push *push)+{+ transport_local *t = (transport_local *)transport;+ git_odb *remote_odb = NULL;+ git_odb *local_odb = NULL;+ git_repository *remote_repo = NULL;+ push_spec *spec;+ char *url = NULL;+ int error;+ unsigned int i;+ size_t j;++ if ((error = git_repository_open(&remote_repo, push->remote->url)) < 0)+ return error;++ /* We don't currently support pushing locally to non-bare repos. Proper + non-bare repo push support would require checking configs to see if+ we should override the default 'don't let this happen' behavior */+ if (!remote_repo->is_bare) {+ error = -1;+ goto on_error;+ }++ if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 ||+ (error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0)+ goto on_error;++ for (i = 0; i < push->pb->nr_objects; i++) {+ if ((error = local_push_copy_object(local_odb, remote_odb,+ &push->pb->object_list[i])) < 0)+ goto on_error;+ }++ push->unpack_ok = 1;++ git_vector_foreach(&push->specs, j, spec) {+ push_status *status;+ const git_error *last;+ char *ref = spec->rref ? spec->rref : spec->lref;++ status = git__calloc(sizeof(push_status), 1);+ if (!status)+ goto on_error;++ status->ref = git__strdup(ref);+ if (!status->ref) {+ git_push_status_free(status);+ goto on_error;+ }++ error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref,+ &spec->loid, &spec->roid);++ switch (error) {+ case GIT_OK:+ break;+ case GIT_EINVALIDSPEC:+ status->msg = git__strdup("funny refname");+ break;+ case GIT_ENOTFOUND:+ status->msg = git__strdup("Remote branch not found to delete");+ break;+ default:+ last = giterr_last();++ if (last && last->message)+ status->msg = git__strdup(last->message);+ else+ status->msg = git__strdup("Unspecified error encountered");+ break;+ }++ /* failed to allocate memory for a status message */+ if (error < 0 && !status->msg) {+ git_push_status_free(status);+ goto on_error;+ }++ /* failed to insert the ref update status */+ if ((error = git_vector_insert(&push->status, status)) < 0) {+ git_push_status_free(status);+ goto on_error;+ }+ }++ if (push->specs.length) {+ int flags = t->flags;+ url = git__strdup(t->url);++ if (!url || t->parent.close(&t->parent) < 0 ||+ t->parent.connect(&t->parent, url,+ push->remote->cred_acquire_cb, NULL, GIT_DIRECTION_PUSH, flags))+ goto on_error;+ }++ error = 0;++on_error:+ git_repository_free(remote_repo);+ git__free(url);++ return error;+}++typedef struct foreach_data {+ git_transfer_progress *stats;+ git_transfer_progress_callback progress_cb;+ void *progress_payload;+ git_odb_writepack *writepack;+} foreach_data;++static int foreach_cb(void *buf, size_t len, void *payload)+{+ foreach_data *data = (foreach_data*)payload;++ data->stats->received_bytes += len;+ return data->writepack->add(data->writepack, buf, len, data->stats);+}++static int local_download_pack(+ git_transport *transport,+ git_repository *repo,+ git_transfer_progress *stats,+ git_transfer_progress_callback progress_cb,+ void *progress_payload)+{+ transport_local *t = (transport_local*)transport;+ git_revwalk *walk = NULL;+ git_remote_head *rhead;+ unsigned int i;+ int error = -1;+ git_oid oid;+ git_packbuilder *pack = NULL;+ git_odb_writepack *writepack = NULL;+ git_odb *odb = NULL;++ if ((error = git_revwalk_new(&walk, t->repo)) < 0)+ goto cleanup;+ git_revwalk_sorting(walk, GIT_SORT_TIME);++ if ((error = git_packbuilder_new(&pack, t->repo)) < 0)+ goto cleanup;++ stats->total_objects = 0;+ stats->indexed_objects = 0;+ stats->received_objects = 0;+ stats->received_bytes = 0;++ git_vector_foreach(&t->refs, i, rhead) {+ git_object *obj;+ if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)+ goto cleanup;++ if (git_object_type(obj) == GIT_OBJ_COMMIT) {+ /* Revwalker includes only wanted commits */+ error = git_revwalk_push(walk, &rhead->oid);+ if (!git_oid_iszero(&rhead->loid))+ error = git_revwalk_hide(walk, &rhead->loid);+ } else {+ /* Tag or some other wanted object. Add it on its own */+ error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);+ }+ git_object_free(obj);+ }++ /* Walk the objects, building a packfile */+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)+ goto cleanup;++ while ((error = git_revwalk_next(&oid, walk)) == 0) {+ git_commit *commit;++ /* Skip commits we already have */+ if (git_odb_exists(odb, &oid)) continue;++ if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {+ const git_oid *tree_oid = git_commit_tree_id(commit);++ /* Add the commit and its tree */+ if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||+ (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0) {+ git_commit_free(commit);+ goto cleanup;+ }++ git_commit_free(commit);+ }+ }++ if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)+ goto cleanup;++ /* Write the data to the ODB */+ {+ foreach_data data = {0};+ data.stats = stats;+ data.progress_cb = progress_cb;+ data.progress_payload = progress_payload;+ data.writepack = writepack;++ if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) < 0)+ goto cleanup;+ }+ error = writepack->commit(writepack, stats);++cleanup:+ if (writepack) writepack->free(writepack);+ git_packbuilder_free(pack);+ git_revwalk_free(walk);+ return error;+}++static int local_is_connected(git_transport *transport)+{+ transport_local *t = (transport_local *)transport;++ return t->connected;+}++static int local_read_flags(git_transport *transport, int *flags)+{+ transport_local *t = (transport_local *)transport;++ *flags = t->flags;++ return 0;+}++static void local_cancel(git_transport *transport)+{+ transport_local *t = (transport_local *)transport;++ git_atomic_set(&t->cancelled, 1);+}+ static int local_close(git_transport *transport) { transport_local *t = (transport_local *)transport; - git_repository_free(t->repo);- t->repo = NULL;+ t->connected = 0; + if (t->repo) {+ git_repository_free(t->repo);+ t->repo = NULL;+ }++ if (t->url) {+ git__free(t->url);+ t->url = NULL;+ }+ return 0; } static void local_free(git_transport *transport) {- unsigned int i;- transport_local *t = (transport_local *) transport;- git_vector *vec = &t->refs;- git_remote_head *h;+ transport_local *t = (transport_local *)transport;+ size_t i;+ git_remote_head *head; - assert(transport);+ /* Close the transport, if it's still open. */+ local_close(transport); - git_vector_foreach (vec, i, h) {- git__free(h->name);- git__free(h);+ git_vector_foreach(&t->refs, i, head) {+ git__free(head->name);+ git__free(head); }- git_vector_free(vec); - git__free(t->parent.url);+ git_vector_free(&t->refs);++ /* Free the transport */ git__free(t); } @@ -219,20 +611,28 @@ * Public API * **************/ -int git_transport_local(git_transport **out)+int git_transport_local(git_transport **out, git_remote *owner, void *param) { transport_local *t; - t = git__malloc(sizeof(transport_local));- GITERR_CHECK_ALLOC(t);+ GIT_UNUSED(param); - memset(t, 0x0, sizeof(transport_local));+ t = git__calloc(1, sizeof(transport_local));+ GITERR_CHECK_ALLOC(t); + t->parent.version = GIT_TRANSPORT_VERSION; t->parent.connect = local_connect;- t->parent.ls = local_ls; t->parent.negotiate_fetch = local_negotiate_fetch;+ t->parent.download_pack = local_download_pack;+ t->parent.push = local_push; t->parent.close = local_close; t->parent.free = local_free;+ t->parent.ls = local_ls;+ t->parent.is_connected = local_is_connected;+ t->parent.read_flags = local_read_flags;+ t->parent.cancel = local_cancel;++ t->owner = owner; *out = (git_transport *) t;
+ libgit2/src/transports/smart.c view
@@ -0,0 +1,345 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#include "git2.h"+#include "smart.h"+#include "refs.h"++static int git_smart__recv_cb(gitno_buffer *buf)+{+ transport_smart *t = (transport_smart *) buf->cb_data;+ size_t old_len, bytes_read;+ int error;++ assert(t->current_stream);++ old_len = buf->offset;++ if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)+ return error;++ buf->offset += bytes_read;++ if (t->packetsize_cb)+ t->packetsize_cb(bytes_read, t->packetsize_payload);++ return (int)(buf->offset - old_len);+}++GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)+{+ if (t->current_stream) {+ t->current_stream->free(t->current_stream);+ t->current_stream = NULL;+ }++ if (close_subtransport &&+ t->wrapped->close(t->wrapped) < 0)+ return -1;++ return 0;+}++static int git_smart__set_callbacks(+ git_transport *transport,+ git_transport_message_cb progress_cb,+ git_transport_message_cb error_cb,+ void *message_cb_payload)+{+ transport_smart *t = (transport_smart *)transport;++ t->progress_cb = progress_cb;+ t->error_cb = error_cb;+ t->message_cb_payload = message_cb_payload;++ return 0;+}++static int git_smart__connect(+ git_transport *transport,+ const char *url,+ git_cred_acquire_cb cred_acquire_cb,+ void *cred_acquire_payload,+ int direction,+ int flags)+{+ transport_smart *t = (transport_smart *)transport;+ git_smart_subtransport_stream *stream;+ int error;+ git_pkt *pkt;+ git_pkt_ref *first;+ git_smart_service_t service;++ if (git_smart__reset_stream(t, true) < 0)+ return -1;++ t->url = git__strdup(url);+ GITERR_CHECK_ALLOC(t->url);++ t->direction = direction;+ t->flags = flags;+ t->cred_acquire_cb = cred_acquire_cb;+ t->cred_acquire_payload = cred_acquire_payload;++ if (GIT_DIRECTION_FETCH == t->direction)+ service = GIT_SERVICE_UPLOADPACK_LS;+ else if (GIT_DIRECTION_PUSH == t->direction)+ service = GIT_SERVICE_RECEIVEPACK_LS;+ else {+ giterr_set(GITERR_NET, "Invalid direction");+ return -1;+ }++ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)+ return error;++ /* Save off the current stream (i.e. socket) that we are working with */+ t->current_stream = stream;++ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);++ /* 2 flushes for RPC; 1 for stateful */+ if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)+ return error;++ /* Strip the comment packet for RPC */+ if (t->rpc) {+ pkt = (git_pkt *)git_vector_get(&t->refs, 0);++ if (!pkt || GIT_PKT_COMMENT != pkt->type) {+ giterr_set(GITERR_NET, "Invalid response");+ return -1;+ } else {+ /* Remove the comment pkt from the list */+ git_vector_remove(&t->refs, 0);+ git__free(pkt);+ }+ }++ /* We now have loaded the refs. */+ t->have_refs = 1;++ first = (git_pkt_ref *)git_vector_get(&t->refs, 0);++ /* Detect capabilities */+ if (git_smart__detect_caps(first, &t->caps) < 0)+ return -1;++ /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */+ if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&+ git_oid_iszero(&first->head.oid)) {+ git_vector_clear(&t->refs);+ git_pkt_free((git_pkt *)first);+ }++ if (t->rpc && git_smart__reset_stream(t, false) < 0)+ return -1;++ /* We're now logically connected. */+ t->connected = 1;++ return 0;+}++static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload)+{+ transport_smart *t = (transport_smart *)transport;+ unsigned int i;+ git_pkt *p = NULL;++ if (!t->have_refs) {+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");+ return -1;+ }++ git_vector_foreach(&t->refs, i, p) {+ git_pkt_ref *pkt = NULL;++ if (p->type != GIT_PKT_REF)+ continue;++ pkt = (git_pkt_ref *)p;++ if (list_cb(&pkt->head, payload))+ return GIT_EUSER;+ }++ return 0;+}++int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)+{+ transport_smart *t = (transport_smart *)transport;+ git_smart_subtransport_stream *stream;+ int error;++ if (t->rpc && git_smart__reset_stream(t, false) < 0)+ return -1;++ if (GIT_DIRECTION_FETCH != t->direction) {+ giterr_set(GITERR_NET, "This operation is only valid for fetch");+ return -1;+ }++ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)+ return error;++ /* If this is a stateful implementation, the stream we get back should be the same */+ assert(t->rpc || t->current_stream == stream);++ /* Save off the current stream (i.e. socket) that we are working with */+ t->current_stream = stream;++ if ((error = stream->write(stream, (const char *)data, len)) < 0)+ return error;++ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);++ return 0;+}++int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)+{+ int error;++ if (t->rpc && git_smart__reset_stream(t, false) < 0)+ return -1;++ if (GIT_DIRECTION_PUSH != t->direction) {+ giterr_set(GITERR_NET, "This operation is only valid for push");+ return -1;+ }++ if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)+ return error;++ /* If this is a stateful implementation, the stream we get back should be the same */+ assert(t->rpc || t->current_stream == *stream);++ /* Save off the current stream (i.e. socket) that we are working with */+ t->current_stream = *stream;++ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);++ return 0;+}++static void git_smart__cancel(git_transport *transport)+{+ transport_smart *t = (transport_smart *)transport;++ git_atomic_set(&t->cancelled, 1);+}++static int git_smart__is_connected(git_transport *transport)+{+ transport_smart *t = (transport_smart *)transport;++ return t->connected;+}++static int git_smart__read_flags(git_transport *transport, int *flags)+{+ transport_smart *t = (transport_smart *)transport;++ *flags = t->flags;++ return 0;+}++static int git_smart__close(git_transport *transport)+{+ transport_smart *t = (transport_smart *)transport;+ git_vector *common = &t->common;+ unsigned int i;+ git_pkt *p;+ int ret;++ ret = git_smart__reset_stream(t, true);++ git_vector_foreach(common, i, p)+ git_pkt_free(p);++ git_vector_free(common);++ if (t->url) {+ git__free(t->url);+ t->url = NULL;+ }++ t->connected = 0;++ return ret;+}++static void git_smart__free(git_transport *transport)+{+ transport_smart *t = (transport_smart *)transport;+ git_vector *refs = &t->refs;+ unsigned int i;+ git_pkt *p;++ /* Make sure that the current stream is closed, if we have one. */+ git_smart__close(transport);++ /* Free the subtransport */+ t->wrapped->free(t->wrapped);++ git_vector_foreach(refs, i, p)+ git_pkt_free(p);++ git_vector_free(refs);++ git__free(t);+}++static int ref_name_cmp(const void *a, const void *b)+{+ const git_pkt_ref *ref_a = a, *ref_b = b;++ return strcmp(ref_a->head.name, ref_b->head.name);+}++int git_transport_smart(git_transport **out, git_remote *owner, void *param)+{+ transport_smart *t;+ git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;++ if (!param)+ return -1;++ t = git__calloc(sizeof(transport_smart), 1);+ GITERR_CHECK_ALLOC(t);++ t->parent.version = GIT_TRANSPORT_VERSION;+ t->parent.set_callbacks = git_smart__set_callbacks;+ t->parent.connect = git_smart__connect;+ t->parent.close = git_smart__close;+ t->parent.free = git_smart__free;+ t->parent.negotiate_fetch = git_smart__negotiate_fetch;+ t->parent.download_pack = git_smart__download_pack;+ t->parent.push = git_smart__push;+ t->parent.ls = git_smart__ls;+ t->parent.is_connected = git_smart__is_connected;+ t->parent.read_flags = git_smart__read_flags;+ t->parent.cancel = git_smart__cancel;+ + t->owner = owner;+ t->rpc = definition->rpc;++ if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {+ git__free(t);+ return -1;+ }++ if (definition->callback(&t->wrapped, &t->parent) < 0) {+ git__free(t);+ return -1;+ } ++ *out = (git_transport *) t;+ return 0;+}
+ libgit2/src/transports/smart_pkt.c view
@@ -0,0 +1,546 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "common.h"++#include "git2/types.h"+#include "git2/errors.h"+#include "git2/refs.h"+#include "git2/revwalk.h"++#include "smart.h"+#include "util.h"+#include "netops.h"+#include "posix.h"+#include "buffer.h"++#include <ctype.h>++#define PKT_LEN_SIZE 4+static const char pkt_done_str[] = "0009done\n";+static const char pkt_flush_str[] = "0000";+static const char pkt_have_prefix[] = "0032have ";+static const char pkt_want_prefix[] = "0032want ";++static int flush_pkt(git_pkt **out)+{+ git_pkt *pkt;++ pkt = git__malloc(sizeof(git_pkt));+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_FLUSH;+ *out = pkt;++ return 0;+}++/* the rest of the line will be useful for multi_ack */+static int ack_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_ack *pkt;+ GIT_UNUSED(line);+ GIT_UNUSED(len);++ pkt = git__calloc(1, sizeof(git_pkt_ack));+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_ACK;+ line += 3;+ len -= 3;++ if (len >= GIT_OID_HEXSZ) {+ git_oid_fromstr(&pkt->oid, line + 1);+ line += GIT_OID_HEXSZ + 1;+ len -= GIT_OID_HEXSZ + 1;+ }++ if (len >= 7) {+ if (!git__prefixcmp(line + 1, "continue"))+ pkt->status = GIT_ACK_CONTINUE;+ }++ *out = (git_pkt *) pkt;++ return 0;+}++static int nak_pkt(git_pkt **out)+{+ git_pkt *pkt;++ pkt = git__malloc(sizeof(git_pkt));+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_NAK;+ *out = pkt;++ return 0;+}++static int pack_pkt(git_pkt **out)+{+ git_pkt *pkt;++ pkt = git__malloc(sizeof(git_pkt));+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_PACK;+ *out = pkt;++ return 0;+}++static int comment_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_comment *pkt;++ pkt = git__malloc(sizeof(git_pkt_comment) + len + 1);+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_COMMENT;+ memcpy(pkt->comment, line, len);+ pkt->comment[len] = '\0';++ *out = (git_pkt *) pkt;++ return 0;+}++static int err_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_err *pkt;++ /* Remove "ERR " from the line */+ line += 4;+ len -= 4;+ pkt = git__malloc(sizeof(git_pkt_err) + len + 1);+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_ERR;+ pkt->len = (int)len;+ memcpy(pkt->error, line, len);+ pkt->error[len] = '\0';++ *out = (git_pkt *) pkt;++ return 0;+}++static int data_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_data *pkt;++ line++;+ len--;+ pkt = git__malloc(sizeof(git_pkt_data) + len);+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_DATA;+ pkt->len = (int) len;+ memcpy(pkt->data, line, len);++ *out = (git_pkt *) pkt;++ return 0;+}++static int progress_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_progress *pkt;++ line++;+ len--;+ pkt = git__malloc(sizeof(git_pkt_progress) + len);+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_PROGRESS;+ pkt->len = (int) len;+ memcpy(pkt->data, line, len);++ *out = (git_pkt *) pkt;++ return 0;+}++static int sideband_error_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_err *pkt;++ line++;+ len--;+ pkt = git__malloc(sizeof(git_pkt_err) + len + 1);+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_ERR;+ pkt->len = (int)len;+ memcpy(pkt->error, line, len);+ pkt->error[len] = '\0';++ *out = (git_pkt *)pkt;++ return 0;+}++/*+ * Parse an other-ref line.+ */+static int ref_pkt(git_pkt **out, const char *line, size_t len)+{+ int error;+ git_pkt_ref *pkt;++ pkt = git__malloc(sizeof(git_pkt_ref));+ GITERR_CHECK_ALLOC(pkt);++ memset(pkt, 0x0, sizeof(git_pkt_ref));+ pkt->type = GIT_PKT_REF;+ if ((error = git_oid_fromstr(&pkt->head.oid, line)) < 0)+ goto error_out;++ /* Check for a bit of consistency */+ if (line[GIT_OID_HEXSZ] != ' ') {+ giterr_set(GITERR_NET, "Error parsing pkt-line");+ error = -1;+ goto error_out;+ }++ /* Jump from the name */+ line += GIT_OID_HEXSZ + 1;+ len -= (GIT_OID_HEXSZ + 1);++ if (line[len - 1] == '\n')+ --len;++ pkt->head.name = git__malloc(len + 1);+ GITERR_CHECK_ALLOC(pkt->head.name);++ memcpy(pkt->head.name, line, len);+ pkt->head.name[len] = '\0';++ if (strlen(pkt->head.name) < len) {+ pkt->capabilities = strchr(pkt->head.name, '\0') + 1;+ }++ *out = (git_pkt *)pkt;+ return 0;++error_out:+ git__free(pkt);+ return error;+}++static int ok_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_ok *pkt;+ const char *ptr;++ pkt = git__malloc(sizeof(*pkt));+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_OK;++ line += 3; /* skip "ok " */+ ptr = strchr(line, '\n');+ len = ptr - line;++ pkt->ref = git__malloc(len + 1);+ GITERR_CHECK_ALLOC(pkt->ref);++ memcpy(pkt->ref, line, len);+ pkt->ref[len] = '\0';++ *out = (git_pkt *)pkt;+ return 0;+}++static int ng_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_ng *pkt;+ const char *ptr;++ pkt = git__malloc(sizeof(*pkt));+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_NG;++ line += 3; /* skip "ng " */+ ptr = strchr(line, ' ');+ len = ptr - line;++ pkt->ref = git__malloc(len + 1);+ GITERR_CHECK_ALLOC(pkt->ref);++ memcpy(pkt->ref, line, len);+ pkt->ref[len] = '\0';++ line = ptr + 1;+ ptr = strchr(line, '\n');+ len = ptr - line;++ pkt->msg = git__malloc(len + 1);+ GITERR_CHECK_ALLOC(pkt->msg);++ memcpy(pkt->msg, line, len);+ pkt->msg[len] = '\0';++ *out = (git_pkt *)pkt;+ return 0;+}++static int unpack_pkt(git_pkt **out, const char *line, size_t len)+{+ git_pkt_unpack *pkt;++ GIT_UNUSED(len);++ pkt = git__malloc(sizeof(*pkt));+ GITERR_CHECK_ALLOC(pkt);++ pkt->type = GIT_PKT_UNPACK;+ if (!git__prefixcmp(line, "unpack ok"))+ pkt->unpack_ok = 1;+ else+ pkt->unpack_ok = 0;++ *out = (git_pkt *)pkt;+ return 0;+}++static int32_t parse_len(const char *line)+{+ char num[PKT_LEN_SIZE + 1];+ int i, error;+ int32_t len;+ const char *num_end;++ memcpy(num, line, PKT_LEN_SIZE);+ num[PKT_LEN_SIZE] = '\0';++ for (i = 0; i < PKT_LEN_SIZE; ++i) {+ if (!isxdigit(num[i])) {+ giterr_set(GITERR_NET, "Found invalid hex digit in length");+ return -1;+ }+ }++ if ((error = git__strtol32(&len, num, &num_end, 16)) < 0)+ return error;++ return len;+}++/*+ * As per the documentation, the syntax is:+ *+ * pkt-line = data-pkt / flush-pkt+ * data-pkt = pkt-len pkt-payload+ * pkt-len = 4*(HEXDIG)+ * pkt-payload = (pkt-len -4)*(OCTET)+ * flush-pkt = "0000"+ *+ * Which means that the first four bytes are the length of the line,+ * in ASCII hexadecimal (including itself)+ */++int git_pkt_parse_line(+ git_pkt **head, const char *line, const char **out, size_t bufflen)+{+ int ret;+ int32_t len;++ /* Not even enough for the length */+ if (bufflen > 0 && bufflen < PKT_LEN_SIZE)+ return GIT_EBUFS;++ len = parse_len(line);+ if (len < 0) {+ /*+ * If we fail to parse the length, it might be because the+ * server is trying to send us the packfile already.+ */+ if (bufflen >= 4 && !git__prefixcmp(line, "PACK")) {+ giterr_clear();+ *out = line;+ return pack_pkt(head);+ }++ return (int)len;+ }++ /*+ * If we were given a buffer length, then make sure there is+ * enough in the buffer to satisfy this line+ */+ if (bufflen > 0 && bufflen < (size_t)len)+ return GIT_EBUFS;++ line += PKT_LEN_SIZE;+ /*+ * TODO: How do we deal with empty lines? Try again? with the next+ * line?+ */+ if (len == PKT_LEN_SIZE) {+ *out = line;+ return 0;+ }++ if (len == 0) { /* Flush pkt */+ *out = line;+ return flush_pkt(head);+ }++ len -= PKT_LEN_SIZE; /* the encoded length includes its own size */++ if (*line == GIT_SIDE_BAND_DATA)+ ret = data_pkt(head, line, len);+ else if (*line == GIT_SIDE_BAND_PROGRESS)+ ret = progress_pkt(head, line, len);+ else if (*line == GIT_SIDE_BAND_ERROR)+ ret = sideband_error_pkt(head, line, len);+ else if (!git__prefixcmp(line, "ACK"))+ ret = ack_pkt(head, line, len);+ else if (!git__prefixcmp(line, "NAK"))+ ret = nak_pkt(head);+ else if (!git__prefixcmp(line, "ERR "))+ ret = err_pkt(head, line, len);+ else if (*line == '#')+ ret = comment_pkt(head, line, len);+ else if (!git__prefixcmp(line, "ok"))+ ret = ok_pkt(head, line, len);+ else if (!git__prefixcmp(line, "ng"))+ ret = ng_pkt(head, line, len);+ else if (!git__prefixcmp(line, "unpack"))+ ret = unpack_pkt(head, line, len);+ else+ ret = ref_pkt(head, line, len);++ *out = line + len;++ return ret;+}++void git_pkt_free(git_pkt *pkt)+{+ if (pkt->type == GIT_PKT_REF) {+ git_pkt_ref *p = (git_pkt_ref *) pkt;+ git__free(p->head.name);+ }++ if (pkt->type == GIT_PKT_OK) {+ git_pkt_ok *p = (git_pkt_ok *) pkt;+ git__free(p->ref);+ }++ if (pkt->type == GIT_PKT_NG) {+ git_pkt_ng *p = (git_pkt_ng *) pkt;+ git__free(p->ref);+ git__free(p->msg);+ }++ git__free(pkt);+}++int git_pkt_buffer_flush(git_buf *buf)+{+ return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));+}++static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf)+{+ git_buf str = GIT_BUF_INIT;+ char oid[GIT_OID_HEXSZ +1] = {0};+ unsigned int len;++ /* Prefer side-band-64k if the server supports both */+ if (caps->side_band) {+ if (caps->side_band_64k)+ git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K);+ else+ git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND);+ }+ if (caps->ofs_delta)+ git_buf_puts(&str, GIT_CAP_OFS_DELTA " ");++ if (caps->multi_ack)+ git_buf_puts(&str, GIT_CAP_MULTI_ACK " ");++ if (caps->include_tag)+ git_buf_puts(&str, GIT_CAP_INCLUDE_TAG " ");++ if (git_buf_oom(&str))+ return -1;++ len = (unsigned int)+ (strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ ++ git_buf_len(&str) + 1 /* LF */);+ git_buf_grow(buf, git_buf_len(buf) + len);+ git_oid_fmt(oid, &head->oid);+ git_buf_printf(buf, "%04xwant %s %s\n", len, oid, git_buf_cstr(&str));+ git_buf_free(&str);++ return git_buf_oom(buf);+}++/*+ * All "want" packets have the same length and format, so what we do+ * is overwrite the OID each time.+ */++int git_pkt_buffer_wants(+ const git_remote_head * const *refs,+ size_t count,+ transport_smart_caps *caps,+ git_buf *buf)+{+ size_t i = 0;+ const git_remote_head *head;++ if (caps->common) {+ for (; i < count; ++i) {+ head = refs[i];+ if (!head->local)+ break;+ }++ if (buffer_want_with_caps(refs[i], caps, buf) < 0)+ return -1;++ i++;+ }++ for (; i < count; ++i) {+ char oid[GIT_OID_HEXSZ];++ head = refs[i];+ if (head->local)+ continue;++ git_oid_fmt(oid, &head->oid);+ git_buf_put(buf, pkt_want_prefix, strlen(pkt_want_prefix));+ git_buf_put(buf, oid, GIT_OID_HEXSZ);+ git_buf_putc(buf, '\n');+ if (git_buf_oom(buf))+ return -1;+ }++ return git_pkt_buffer_flush(buf);+}++int git_pkt_buffer_have(git_oid *oid, git_buf *buf)+{+ char oidhex[GIT_OID_HEXSZ + 1];++ memset(oidhex, 0x0, sizeof(oidhex));+ git_oid_fmt(oidhex, oid);+ return git_buf_printf(buf, "%s%s\n", pkt_have_prefix, oidhex);+}++int git_pkt_buffer_done(git_buf *buf)+{+ return git_buf_puts(buf, pkt_done_str);+}
+ libgit2/src/transports/smart_protocol.c view
@@ -0,0 +1,856 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#include "git2.h"++#include "smart.h"+#include "refs.h"+#include "repository.h"+#include "push.h"+#include "pack-objects.h"+#include "remote.h"++#define NETWORK_XFER_THRESHOLD (100*1024)++int git_smart__store_refs(transport_smart *t, int flushes)+{+ gitno_buffer *buf = &t->buffer;+ git_vector *refs = &t->refs;+ int error, flush = 0, recvd;+ const char *line_end;+ git_pkt *pkt;++ /* Clear existing refs in case git_remote_connect() is called again+ * after git_remote_disconnect().+ */+ git_vector_clear(refs);++ do {+ if (buf->offset > 0)+ error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);+ else+ error = GIT_EBUFS;++ if (error < 0 && error != GIT_EBUFS)+ return -1;++ if (error == GIT_EBUFS) {+ if ((recvd = gitno_recv(buf)) < 0)+ return -1;++ if (recvd == 0 && !flush) {+ giterr_set(GITERR_NET, "Early EOF");+ return -1;+ }++ continue;+ }++ gitno_consume(buf, line_end);+ if (pkt->type == GIT_PKT_ERR) {+ giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);+ git__free(pkt);+ return -1;+ }++ if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0)+ return -1;++ if (pkt->type == GIT_PKT_FLUSH) {+ flush++;+ git_pkt_free(pkt);+ }+ } while (flush < flushes);++ return flush;+}++int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)+{+ const char *ptr;++ /* No refs or capabilites, odd but not a problem */+ if (pkt == NULL || pkt->capabilities == NULL)+ return 0;++ ptr = pkt->capabilities;+ while (ptr != NULL && *ptr != '\0') {+ if (*ptr == ' ')+ ptr++;++ if (!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {+ caps->common = caps->ofs_delta = 1;+ ptr += strlen(GIT_CAP_OFS_DELTA);+ continue;+ }++ if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {+ caps->common = caps->multi_ack = 1;+ ptr += strlen(GIT_CAP_MULTI_ACK);+ continue;+ }++ if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {+ caps->common = caps->include_tag = 1;+ ptr += strlen(GIT_CAP_INCLUDE_TAG);+ continue;+ }++ /* Keep side-band check after side-band-64k */+ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {+ caps->common = caps->side_band_64k = 1;+ ptr += strlen(GIT_CAP_SIDE_BAND_64K);+ continue;+ }++ if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {+ caps->common = caps->side_band = 1;+ ptr += strlen(GIT_CAP_SIDE_BAND);+ continue;+ }++ if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) {+ caps->common = caps->delete_refs = 1;+ ptr += strlen(GIT_CAP_DELETE_REFS);+ continue;+ }++ /* We don't know this capability, so skip it */+ ptr = strchr(ptr, ' ');+ }++ return 0;+}++static int recv_pkt(git_pkt **out, gitno_buffer *buf)+{+ const char *ptr = buf->data, *line_end = ptr;+ git_pkt *pkt;+ int pkt_type, error = 0, ret;++ do {+ if (buf->offset > 0)+ error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);+ else+ error = GIT_EBUFS;++ if (error == 0)+ break; /* return the pkt */++ if (error < 0 && error != GIT_EBUFS)+ return -1;++ if ((ret = gitno_recv(buf)) < 0)+ return -1;+ } while (error);++ gitno_consume(buf, line_end);+ pkt_type = pkt->type;+ if (out != NULL)+ *out = pkt;+ else+ git__free(pkt);++ return pkt_type;+}++static int store_common(transport_smart *t)+{+ git_pkt *pkt = NULL;+ gitno_buffer *buf = &t->buffer;++ do {+ if (recv_pkt(&pkt, buf) < 0)+ return -1;++ if (pkt->type == GIT_PKT_ACK) {+ if (git_vector_insert(&t->common, pkt) < 0)+ return -1;+ } else {+ git__free(pkt);+ return 0;+ }++ } while (1);++ return 0;+}++static int fetch_setup_walk(git_revwalk **out, git_repository *repo)+{+ git_revwalk *walk;+ git_strarray refs;+ unsigned int i;+ git_reference *ref;++ if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)+ return -1;++ if (git_revwalk_new(&walk, repo) < 0)+ return -1;++ git_revwalk_sorting(walk, GIT_SORT_TIME);++ for (i = 0; i < refs.count; ++i) {+ /* No tags */+ if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))+ continue;++ if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0)+ goto on_error;++ if (git_reference_type(ref) == GIT_REF_SYMBOLIC)+ continue;+ if (git_revwalk_push(walk, git_reference_target(ref)) < 0)+ goto on_error;++ git_reference_free(ref);+ }++ git_strarray_free(&refs);+ *out = walk;+ return 0;++on_error:+ git_reference_free(ref);+ git_strarray_free(&refs);+ return -1;+}++int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count)+{+ transport_smart *t = (transport_smart *)transport;+ gitno_buffer *buf = &t->buffer;+ git_buf data = GIT_BUF_INIT;+ git_revwalk *walk = NULL;+ int error = -1, pkt_type;+ unsigned int i;+ git_oid oid;++ /* No own logic, do our thing */+ if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)+ return error;++ if ((error = fetch_setup_walk(&walk, repo)) < 0)+ goto on_error;+ /*+ * We don't support any kind of ACK extensions, so the negotiation+ * boils down to sending what we have and listening for an ACK+ * every once in a while.+ */+ i = 0;+ while (true) {+ error = git_revwalk_next(&oid, walk);++ if (error < 0) {+ if (GIT_ITEROVER == error)+ break;++ goto on_error;+ }++ git_pkt_buffer_have(&oid, &data);+ i++;+ if (i % 20 == 0) {+ if (t->cancelled.val) {+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");+ error = GIT_EUSER;+ goto on_error;+ }++ git_pkt_buffer_flush(&data);+ if (git_buf_oom(&data)) {+ error = -1;+ goto on_error;+ }++ if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0)+ goto on_error;++ git_buf_clear(&data);+ if (t->caps.multi_ack) {+ if ((error = store_common(t)) < 0)+ goto on_error;+ } else {+ pkt_type = recv_pkt(NULL, buf);++ if (pkt_type == GIT_PKT_ACK) {+ break;+ } else if (pkt_type == GIT_PKT_NAK) {+ continue;+ } else if (pkt_type < 0) {+ /* recv_pkt returned an error */+ error = pkt_type;+ goto on_error;+ } else {+ giterr_set(GITERR_NET, "Unexpected pkt type");+ error = -1;+ goto on_error;+ }+ }+ }++ if (t->common.length > 0)+ break;++ if (i % 20 == 0 && t->rpc) {+ git_pkt_ack *pkt;+ unsigned int i;++ if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)+ goto on_error;++ git_vector_foreach(&t->common, i, pkt) {+ if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0)+ goto on_error;+ }++ if (git_buf_oom(&data)) {+ error = -1;+ goto on_error;+ }+ }+ }++ /* Tell the other end that we're done negotiating */+ if (t->rpc && t->common.length > 0) {+ git_pkt_ack *pkt;+ unsigned int i;++ if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)+ goto on_error;++ git_vector_foreach(&t->common, i, pkt) {+ if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0)+ goto on_error;+ }++ if (git_buf_oom(&data)) {+ error = -1;+ goto on_error;+ }+ }++ if ((error = git_pkt_buffer_done(&data)) < 0)+ goto on_error;++ if (t->cancelled.val) {+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");+ error = GIT_EUSER;+ goto on_error;+ }+ if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0)+ goto on_error;++ git_buf_free(&data);+ git_revwalk_free(walk);++ /* Now let's eat up whatever the server gives us */+ if (!t->caps.multi_ack) {+ pkt_type = recv_pkt(NULL, buf);++ if (pkt_type < 0) {+ return pkt_type;+ } else if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {+ giterr_set(GITERR_NET, "Unexpected pkt type");+ return -1;+ }+ } else {+ git_pkt_ack *pkt;+ do {+ if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0)+ return error;++ if (pkt->type == GIT_PKT_NAK ||+ (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {+ git__free(pkt);+ break;+ }++ git__free(pkt);+ } while (1);+ }++ return 0;++on_error:+ git_revwalk_free(walk);+ git_buf_free(&data);+ return error;+}++static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_transfer_progress *stats)+{+ int recvd;++ do {+ if (t->cancelled.val) {+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");+ return GIT_EUSER;+ }++ if (writepack->add(writepack, buf->data, buf->offset, stats) < 0)+ return -1;++ gitno_consume_n(buf, buf->offset);++ if ((recvd = gitno_recv(buf)) < 0)+ return -1;+ } while(recvd > 0);++ if (writepack->commit(writepack, stats))+ return -1;++ return 0;+}++struct network_packetsize_payload+{+ git_transfer_progress_callback callback;+ void *payload;+ git_transfer_progress *stats;+ size_t last_fired_bytes;+};++static void network_packetsize(size_t received, void *payload)+{+ struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload;++ /* Accumulate bytes */+ npp->stats->received_bytes += received;++ /* Fire notification if the threshold is reached */+ if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) {+ npp->last_fired_bytes = npp->stats->received_bytes;+ npp->callback(npp->stats, npp->payload);+ }+}++int git_smart__download_pack(+ git_transport *transport,+ git_repository *repo,+ git_transfer_progress *stats,+ git_transfer_progress_callback progress_cb,+ void *progress_payload)+{+ transport_smart *t = (transport_smart *)transport;+ gitno_buffer *buf = &t->buffer;+ git_odb *odb;+ struct git_odb_writepack *writepack = NULL;+ int error = -1;+ struct network_packetsize_payload npp = {0};++ memset(stats, 0, sizeof(git_transfer_progress));++ if (progress_cb) {+ npp.callback = progress_cb;+ npp.payload = progress_payload;+ npp.stats = stats;+ t->packetsize_cb = &network_packetsize;+ t->packetsize_payload = &npp;++ /* We might have something in the buffer already from negotiate_fetch */+ if (t->buffer.offset > 0)+ t->packetsize_cb(t->buffer.offset, t->packetsize_payload);+ }++ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||+ ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0))+ goto on_error;++ /*+ * If the remote doesn't support the side-band, we can feed+ * the data directly to the pack writer. Otherwise, we need to+ * check which one belongs there.+ */+ if (!t->caps.side_band && !t->caps.side_band_64k) {+ if (no_sideband(t, writepack, buf, stats) < 0)+ goto on_error;++ goto on_success;+ }++ do {+ git_pkt *pkt;++ if (t->cancelled.val) {+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");+ error = GIT_EUSER;+ goto on_error;+ }++ if (recv_pkt(&pkt, buf) < 0)+ goto on_error;++ if (pkt->type == GIT_PKT_PROGRESS) {+ if (t->progress_cb) {+ git_pkt_progress *p = (git_pkt_progress *) pkt;+ t->progress_cb(p->data, p->len, t->message_cb_payload);+ }+ git__free(pkt);+ } else if (pkt->type == GIT_PKT_DATA) {+ git_pkt_data *p = (git_pkt_data *) pkt;+ error = writepack->add(writepack, p->data, p->len, stats);++ git__free(pkt);+ if (error < 0)+ goto on_error;+ } else if (pkt->type == GIT_PKT_FLUSH) {+ /* A flush indicates the end of the packfile */+ git__free(pkt);+ break;+ }+ } while (1);++ if (writepack->commit(writepack, stats) < 0)+ goto on_error;++on_success:+ error = 0;++on_error:+ if (writepack)+ writepack->free(writepack);++ /* Trailing execution of progress_cb, if necessary */+ if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes)+ npp.callback(npp.stats, npp.payload);++ return error;+}++static int gen_pktline(git_buf *buf, git_push *push)+{+ push_spec *spec;+ size_t i, len;+ char old_id[41], new_id[41];++ old_id[40] = '\0'; new_id[40] = '\0';++ git_vector_foreach(&push->specs, i, spec) {+ len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->rref);++ if (i == 0) {+ ++len; /* '\0' */+ if (push->report_status)+ len += strlen(GIT_CAP_REPORT_STATUS) + 1;+ len += strlen(GIT_CAP_SIDE_BAND_64K) + 1;+ }++ git_oid_fmt(old_id, &spec->roid);+ git_oid_fmt(new_id, &spec->loid);++ git_buf_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->rref);++ if (i == 0) {+ git_buf_putc(buf, '\0');+ /* Core git always starts their capabilities string with a space */+ if (push->report_status) {+ git_buf_putc(buf, ' ');+ git_buf_printf(buf, GIT_CAP_REPORT_STATUS);+ }+ git_buf_putc(buf, ' ');+ git_buf_printf(buf, GIT_CAP_SIDE_BAND_64K);+ }++ git_buf_putc(buf, '\n');+ }++ git_buf_puts(buf, "0000");+ return git_buf_oom(buf) ? -1 : 0;+}++static int add_push_report_pkt(git_push *push, git_pkt *pkt)+{+ push_status *status;++ switch (pkt->type) {+ case GIT_PKT_OK:+ status = git__malloc(sizeof(push_status));+ GITERR_CHECK_ALLOC(status);+ status->msg = NULL;+ status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);+ if (!status->ref ||+ git_vector_insert(&push->status, status) < 0) {+ git_push_status_free(status);+ return -1;+ }+ break;+ case GIT_PKT_NG:+ status = git__calloc(sizeof(push_status), 1);+ GITERR_CHECK_ALLOC(status);+ status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);+ status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);+ if (!status->ref || !status->msg ||+ git_vector_insert(&push->status, status) < 0) {+ git_push_status_free(status);+ return -1;+ }+ break;+ case GIT_PKT_UNPACK:+ push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;+ break;+ case GIT_PKT_FLUSH:+ return GIT_ITEROVER;+ default:+ giterr_set(GITERR_NET, "report-status: protocol error");+ return -1;+ }++ return 0;+}++static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt)+{+ git_pkt *pkt;+ const char *line = data_pkt->data, *line_end;+ size_t line_len = data_pkt->len;+ int error;++ while (line_len > 0) {+ error = git_pkt_parse_line(&pkt, line, &line_end, line_len);++ if (error < 0)+ return error;++ /* Advance in the buffer */+ line_len -= (line_end - line);+ line = line_end;++ error = add_push_report_pkt(push, pkt);++ git_pkt_free(pkt);++ if (error < 0 && error != GIT_ITEROVER)+ return error;+ }++ return 0;+}++static int parse_report(gitno_buffer *buf, git_push *push)+{+ git_pkt *pkt;+ const char *line_end;+ int error, recvd;++ for (;;) {+ if (buf->offset > 0)+ error = git_pkt_parse_line(&pkt, buf->data,+ &line_end, buf->offset);+ else+ error = GIT_EBUFS;++ if (error < 0 && error != GIT_EBUFS)+ return -1;++ if (error == GIT_EBUFS) {+ if ((recvd = gitno_recv(buf)) < 0)+ return -1;++ if (recvd == 0) {+ giterr_set(GITERR_NET, "Early EOF");+ return -1;+ }+ continue;+ }++ gitno_consume(buf, line_end);++ error = 0;++ switch (pkt->type) {+ case GIT_PKT_DATA:+ /* This is a sideband packet which contains other packets */+ error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt);+ break;+ case GIT_PKT_ERR:+ giterr_set(GITERR_NET, "report-status: Error reported: %s",+ ((git_pkt_err *)pkt)->error);+ error = -1;+ break;+ case GIT_PKT_PROGRESS:+ break;+ default:+ error = add_push_report_pkt(push, pkt);+ break;+ }++ git_pkt_free(pkt);++ /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */+ if (error == GIT_ITEROVER)+ return 0;++ if (error < 0)+ return error;+ }+}++static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec)+{+ git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref));+ GITERR_CHECK_ALLOC(added);++ added->type = GIT_PKT_REF;+ git_oid_cpy(&added->head.oid, &push_spec->loid);+ added->head.name = git__strdup(push_spec->rref);++ if (!added->head.name ||+ git_vector_insert(refs, added) < 0) {+ git_pkt_free((git_pkt *)added);+ return -1;+ }++ return 0;+}++static int update_refs_from_report(+ git_vector *refs,+ git_vector *push_specs,+ git_vector *push_report)+{+ git_pkt_ref *ref;+ push_spec *push_spec;+ push_status *push_status;+ size_t i, j, refs_len;+ int cmp;++ /* For each push spec we sent to the server, we should have+ * gotten back a status packet in the push report */+ if (push_specs->length != push_report->length) {+ giterr_set(GITERR_NET, "report-status: protocol error");+ return -1;+ }++ /* We require that push_specs be sorted with push_spec_rref_cmp,+ * and that push_report be sorted with push_status_ref_cmp */+ git_vector_sort(push_specs);+ git_vector_sort(push_report);++ git_vector_foreach(push_specs, i, push_spec) {+ push_status = git_vector_get(push_report, i);++ /* For each push spec we sent to the server, we should have+ * gotten back a status packet in the push report which matches */+ if (strcmp(push_spec->rref, push_status->ref)) {+ giterr_set(GITERR_NET, "report-status: protocol error");+ return -1;+ }+ }++ /* We require that refs be sorted with ref_name_cmp */+ git_vector_sort(refs);+ i = j = 0;+ refs_len = refs->length;++ /* Merge join push_specs with refs */+ while (i < push_specs->length && j < refs_len) {+ push_spec = git_vector_get(push_specs, i);+ push_status = git_vector_get(push_report, i);+ ref = git_vector_get(refs, j);++ cmp = strcmp(push_spec->rref, ref->head.name);++ /* Iterate appropriately */+ if (cmp <= 0) i++;+ if (cmp >= 0) j++;++ /* Add case */+ if (cmp < 0 &&+ !push_status->msg &&+ add_ref_from_push_spec(refs, push_spec) < 0)+ return -1;++ /* Update case, delete case */+ if (cmp == 0 &&+ !push_status->msg)+ git_oid_cpy(&ref->head.oid, &push_spec->loid);+ }++ for (; i < push_specs->length; i++) {+ push_spec = git_vector_get(push_specs, i);+ push_status = git_vector_get(push_report, i);++ /* Add case */+ if (!push_status->msg &&+ add_ref_from_push_spec(refs, push_spec) < 0)+ return -1;+ }++ /* Remove any refs which we updated to have a zero OID. */+ git_vector_rforeach(refs, i, ref) {+ if (git_oid_iszero(&ref->head.oid)) {+ git_vector_remove(refs, i);+ git_pkt_free((git_pkt *)ref);+ }+ }++ git_vector_sort(refs);++ return 0;+}++static int stream_thunk(void *buf, size_t size, void *data)+{+ git_smart_subtransport_stream *s = (git_smart_subtransport_stream *)data;++ return s->write(s, (const char *)buf, size);+}++int git_smart__push(git_transport *transport, git_push *push)+{+ transport_smart *t = (transport_smart *)transport;+ git_smart_subtransport_stream *s;+ git_buf pktline = GIT_BUF_INIT;+ int error = -1;++#ifdef PUSH_DEBUG+{+ git_remote_head *head;+ push_spec *spec;+ unsigned int i;+ char hex[41]; hex[40] = '\0';++ git_vector_foreach(&push->remote->refs, i, head) {+ git_oid_fmt(hex, &head->oid);+ fprintf(stderr, "%s (%s)\n", hex, head->name);+ }++ git_vector_foreach(&push->specs, i, spec) {+ git_oid_fmt(hex, &spec->roid);+ fprintf(stderr, "%s (%s) -> ", hex, spec->lref);+ git_oid_fmt(hex, &spec->loid);+ fprintf(stderr, "%s (%s)\n", hex, spec->rref ?+ spec->rref : spec->lref);+ }+}+#endif++ if (git_smart__get_push_stream(t, &s) < 0 ||+ gen_pktline(&pktline, push) < 0 ||+ s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0 ||+ git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0)+ goto on_error;++ /* If we sent nothing or the server doesn't support report-status, then+ * we consider the pack to have been unpacked successfully */+ if (!push->specs.length || !push->report_status)+ push->unpack_ok = 1;+ else if (parse_report(&t->buffer, push) < 0)+ goto on_error;++ if (push->status.length &&+ update_refs_from_report(&t->refs, &push->specs, &push->status) < 0)+ goto on_error;++ error = 0;++on_error:+ git_buf_free(&pktline);++ return error;+}
+ libgit2/src/transports/winhttp.c view
@@ -0,0 +1,1136 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#ifdef GIT_WINHTTP++#include "git2.h"+#include "git2/transport.h"+#include "buffer.h"+#include "posix.h"+#include "netops.h"+#include "smart.h"+#include "remote.h"+#include "repository.h"++#include <winhttp.h>+#pragma comment(lib, "winhttp")++/* For UuidCreate */+#pragma comment(lib, "rpcrt4")++#define WIDEN2(s) L ## s+#define WIDEN(s) WIDEN2(s)++#define MAX_CONTENT_TYPE_LEN 100+#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109+#define CACHED_POST_BODY_BUF_SIZE 4096+#define UUID_LENGTH_CCH 32++static const char *prefix_http = "http://";+static const char *prefix_https = "https://";+static const char *upload_pack_service = "upload-pack";+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";+static const char *upload_pack_service_url = "/git-upload-pack";+static const char *receive_pack_service = "receive-pack";+static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";+static const char *receive_pack_service_url = "/git-receive-pack";+static const wchar_t *get_verb = L"GET";+static const wchar_t *post_verb = L"POST";+static const wchar_t *pragma_nocache = L"Pragma: no-cache";+static const wchar_t *transfer_encoding = L"Transfer-Encoding: chunked";+static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |+ SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |+ SECURITY_FLAG_IGNORE_UNKNOWN_CA;++#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)++typedef enum {+ GIT_WINHTTP_AUTH_BASIC = 1,+} winhttp_authmechanism_t;++typedef struct {+ git_smart_subtransport_stream parent;+ const char *service;+ const char *service_url;+ const wchar_t *verb;+ HINTERNET request;+ wchar_t *request_uri;+ char *chunk_buffer;+ unsigned chunk_buffer_len;+ HANDLE post_body;+ DWORD post_body_len;+ unsigned sent_request : 1,+ received_response : 1,+ chunked : 1;+} winhttp_stream;++typedef struct {+ git_smart_subtransport parent;+ transport_smart *owner;+ const char *path;+ char *host;+ char *port;+ char *user_from_url;+ char *pass_from_url;+ git_cred *cred;+ git_cred *url_cred;+ int auth_mechanism;+ HINTERNET session;+ HINTERNET connection;+ unsigned use_ssl : 1;+} winhttp_subtransport;++static int apply_basic_credential(HINTERNET request, git_cred *cred)+{+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;+ git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT;+ wchar_t *wide = NULL;+ int error = -1, wide_len = 0;++ git_buf_printf(&raw, "%s:%s", c->username, c->password);++ if (git_buf_oom(&raw) ||+ git_buf_puts(&buf, "Authorization: Basic ") < 0 ||+ git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0)+ goto on_error;++ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,+ git_buf_cstr(&buf), -1, NULL, 0);++ if (!wide_len) {+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");+ goto on_error;+ }++ wide = git__malloc(wide_len * sizeof(wchar_t));++ if (!wide)+ goto on_error;++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,+ git_buf_cstr(&buf), -1, wide, wide_len)) {+ giterr_set(GITERR_OS, "Failed to convert string to wide form");+ goto on_error;+ }++ if (!WinHttpAddRequestHeaders(request, wide, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {+ giterr_set(GITERR_OS, "Failed to add a header to the request");+ goto on_error;+ }++ error = 0;++on_error:+ /* We were dealing with plaintext passwords, so clean up after ourselves a bit. */+ if (wide)+ memset(wide, 0x0, wide_len * sizeof(wchar_t));++ if (buf.size)+ memset(buf.ptr, 0x0, buf.size);++ if (raw.size)+ memset(raw.ptr, 0x0, raw.size);++ git__free(wide);+ git_buf_free(&buf);+ git_buf_free(&raw);+ return error;+}++static int winhttp_stream_connect(winhttp_stream *s)+{+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);+ git_buf buf = GIT_BUF_INIT;+ char *proxy_url = NULL;+ wchar_t ct[MAX_CONTENT_TYPE_LEN];+ wchar_t *types[] = { L"*/*", NULL };+ BOOL peerdist = FALSE;+ int error = -1, wide_len;++ /* Prepare URL */+ git_buf_printf(&buf, "%s%s", t->path, s->service_url);++ if (git_buf_oom(&buf))+ return -1;++ /* Convert URL to wide characters */+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,+ git_buf_cstr(&buf), -1, NULL, 0);++ if (!wide_len) {+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");+ goto on_error;+ }++ s->request_uri = git__malloc(wide_len * sizeof(wchar_t));++ if (!s->request_uri)+ goto on_error;++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,+ git_buf_cstr(&buf), -1, s->request_uri, wide_len)) {+ giterr_set(GITERR_OS, "Failed to convert string to wide form");+ goto on_error;+ }++ /* Establish request */+ s->request = WinHttpOpenRequest(+ t->connection,+ s->verb,+ s->request_uri,+ NULL,+ WINHTTP_NO_REFERER,+ types,+ t->use_ssl ? WINHTTP_FLAG_SECURE : 0);++ if (!s->request) {+ giterr_set(GITERR_OS, "Failed to open request");+ goto on_error;+ }++ /* Set proxy if necessary */+ if (git_remote__get_http_proxy(t->owner->owner, t->use_ssl, &proxy_url) < 0)+ goto on_error;++ if (proxy_url) {+ WINHTTP_PROXY_INFO proxy_info;+ wchar_t *proxy_wide;++ /* Convert URL to wide characters */+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,+ proxy_url, -1, NULL, 0);++ if (!wide_len) {+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");+ goto on_error;+ }++ proxy_wide = git__malloc(wide_len * sizeof(wchar_t));++ if (!proxy_wide)+ goto on_error;++ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,+ proxy_url, -1, proxy_wide, wide_len)) {+ giterr_set(GITERR_OS, "Failed to convert string to wide form");+ git__free(proxy_wide);+ goto on_error;+ }++ /* Strip any trailing forward slash on the proxy URL;+ * WinHTTP doesn't like it if one is present */+ if (wide_len > 1 && L'/' == proxy_wide[wide_len - 2])+ proxy_wide[wide_len - 2] = L'\0';++ proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;+ proxy_info.lpszProxy = proxy_wide;+ proxy_info.lpszProxyBypass = NULL;++ if (!WinHttpSetOption(s->request,+ WINHTTP_OPTION_PROXY,+ &proxy_info,+ sizeof(WINHTTP_PROXY_INFO))) {+ giterr_set(GITERR_OS, "Failed to set proxy");+ git__free(proxy_wide);+ goto on_error;+ }++ git__free(proxy_wide);+ }++ /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP+ * adds itself. This option may not be supported by the underlying+ * platform, so we do not error-check it */+ WinHttpSetOption(s->request,+ WINHTTP_OPTION_PEERDIST_EXTENSION_STATE,+ &peerdist,+ sizeof(peerdist));++ /* Send Pragma: no-cache header */+ if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {+ giterr_set(GITERR_OS, "Failed to add a header to the request");+ goto on_error;+ }++ /* Send Content-Type header -- only necessary on a POST */+ if (post_verb == s->verb) {+ git_buf_clear(&buf);+ if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0)+ goto on_error;++ git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf));++ if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {+ giterr_set(GITERR_OS, "Failed to add a header to the request");+ goto on_error;+ }+ }++ /* If requested, disable certificate validation */+ if (t->use_ssl) {+ int flags;++ if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)+ goto on_error;++ if ((GIT_TRANSPORTFLAGS_NO_CHECK_CERT & flags) &&+ !WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS,+ (LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {+ giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");+ goto on_error;+ }+ }++ /* If we have a credential on the subtransport, apply it to the request */+ if (t->cred &&+ t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&+ t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC &&+ apply_basic_credential(s->request, t->cred) < 0)+ goto on_error;++ /* If no other credentials have been applied and the URL has username and+ * password, use those */+ if (!t->cred && t->user_from_url && t->pass_from_url) {+ if (!t->url_cred &&+ git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0)+ goto on_error;+ if (apply_basic_credential(s->request, t->url_cred) < 0)+ goto on_error;+ }++ /* We've done everything up to calling WinHttpSendRequest. */++ error = 0;++on_error:+ git__free(proxy_url);+ git_buf_free(&buf);+ return error;+}++static int parse_unauthorized_response(+ HINTERNET request,+ int *allowed_types,+ int *auth_mechanism)+{+ DWORD supported, first, target;++ *allowed_types = 0;+ *auth_mechanism = 0;++ /* WinHttpQueryHeaders() must be called before WinHttpQueryAuthSchemes(). + * We can assume this was already done, since we know we are unauthorized. + */+ if (!WinHttpQueryAuthSchemes(request, &supported, &first, &target)) {+ giterr_set(GITERR_OS, "Failed to parse supported auth schemes"); + return -1;+ }++ if (WINHTTP_AUTH_SCHEME_BASIC & supported) {+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;+ *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;+ }++ return 0;+}++static int write_chunk(HINTERNET request, const char *buffer, size_t len)+{+ DWORD bytes_written;+ git_buf buf = GIT_BUF_INIT;++ /* Chunk header */+ git_buf_printf(&buf, "%X\r\n", len);++ if (git_buf_oom(&buf))+ return -1;++ if (!WinHttpWriteData(request,+ git_buf_cstr(&buf), (DWORD)git_buf_len(&buf),+ &bytes_written)) {+ git_buf_free(&buf);+ giterr_set(GITERR_OS, "Failed to write chunk header");+ return -1;+ }++ git_buf_free(&buf);++ /* Chunk body */+ if (!WinHttpWriteData(request,+ buffer, (DWORD)len,+ &bytes_written)) {+ giterr_set(GITERR_OS, "Failed to write chunk");+ return -1;+ }++ /* Chunk footer */+ if (!WinHttpWriteData(request,+ "\r\n", 2,+ &bytes_written)) {+ giterr_set(GITERR_OS, "Failed to write chunk footer");+ return -1;+ }++ return 0;+}++static int winhttp_stream_read(+ git_smart_subtransport_stream *stream,+ char *buffer,+ size_t buf_size,+ size_t *bytes_read)+{+ winhttp_stream *s = (winhttp_stream *)stream;+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);+ DWORD dw_bytes_read;+ char replay_count = 0;++replay:+ /* Enforce a reasonable cap on the number of replays */+ if (++replay_count >= 7) {+ giterr_set(GITERR_NET, "Too many redirects or authentication replays");+ return -1;+ }++ /* Connect if necessary */+ if (!s->request && winhttp_stream_connect(s) < 0)+ return -1;++ if (!s->received_response) {+ DWORD status_code, status_code_length, content_type_length, bytes_written;+ char expected_content_type_8[MAX_CONTENT_TYPE_LEN];+ wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN];++ if (!s->sent_request) {+ if (!WinHttpSendRequest(s->request,+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,+ WINHTTP_NO_REQUEST_DATA, 0,+ s->post_body_len, 0)) {+ giterr_set(GITERR_OS, "Failed to send request");+ return -1;+ }++ s->sent_request = 1;+ }++ if (s->chunked) {+ assert(s->verb == post_verb);++ /* Flush, if necessary */+ if (s->chunk_buffer_len > 0 &&+ write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)+ return -1;++ s->chunk_buffer_len = 0;++ /* Write the final chunk. */+ if (!WinHttpWriteData(s->request,+ "0\r\n\r\n", 5,+ &bytes_written)) {+ giterr_set(GITERR_OS, "Failed to write final chunk");+ return -1;+ }+ }+ else if (s->post_body) {+ char *buffer;+ DWORD len = s->post_body_len, bytes_read;++ if (INVALID_SET_FILE_POINTER == SetFilePointer(s->post_body,+ 0, 0, FILE_BEGIN) &&+ NO_ERROR != GetLastError()) {+ giterr_set(GITERR_OS, "Failed to reset file pointer");+ return -1;+ }++ buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);++ while (len > 0) {+ DWORD bytes_written;++ if (!ReadFile(s->post_body, buffer,+ min(CACHED_POST_BODY_BUF_SIZE, len),+ &bytes_read, NULL) ||+ !bytes_read) {+ git__free(buffer);+ giterr_set(GITERR_OS, "Failed to read from temp file");+ return -1;+ }++ if (!WinHttpWriteData(s->request, buffer,+ bytes_read, &bytes_written)) {+ git__free(buffer);+ giterr_set(GITERR_OS, "Failed to write data");+ return -1;+ }++ len -= bytes_read;+ assert(bytes_read == bytes_written);+ }++ git__free(buffer);++ /* Eagerly close the temp file */+ CloseHandle(s->post_body);+ s->post_body = NULL;+ }++ if (!WinHttpReceiveResponse(s->request, 0)) {+ giterr_set(GITERR_OS, "Failed to receive response");+ return -1;+ }++ /* Verify that we got a 200 back */+ status_code_length = sizeof(status_code);++ if (!WinHttpQueryHeaders(s->request,+ WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,+ WINHTTP_HEADER_NAME_BY_INDEX,+ &status_code, &status_code_length,+ WINHTTP_NO_HEADER_INDEX)) {+ giterr_set(GITERR_OS, "Failed to retrieve status code");+ return -1;+ }++ /* The implementation of WinHTTP prior to Windows 7 will not+ * redirect to an identical URI. Some Git hosters use self-redirects+ * as part of their DoS mitigation strategy. Check first to see if we+ * have a redirect status code, and that we haven't already streamed+ * a post body. (We can't replay a streamed POST.) */+ if (!s->chunked &&+ (HTTP_STATUS_MOVED == status_code ||+ HTTP_STATUS_REDIRECT == status_code ||+ (HTTP_STATUS_REDIRECT_METHOD == status_code &&+ get_verb == s->verb) ||+ HTTP_STATUS_REDIRECT_KEEP_VERB == status_code)) {++ /* Check for Windows 7. This workaround is only necessary on+ * Windows Vista and earlier. Windows 7 is version 6.1. */+ if (!git_has_win32_version(6, 1)) {+ wchar_t *location;+ DWORD location_length;+ int redirect_cmp;++ /* OK, fetch the Location header from the redirect. */+ if (WinHttpQueryHeaders(s->request,+ WINHTTP_QUERY_LOCATION,+ WINHTTP_HEADER_NAME_BY_INDEX,+ WINHTTP_NO_OUTPUT_BUFFER,+ &location_length,+ WINHTTP_NO_HEADER_INDEX) ||+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) {+ giterr_set(GITERR_OS, "Failed to read Location header");+ return -1;+ }++ location = git__malloc(location_length);+ GITERR_CHECK_ALLOC(location);++ if (!WinHttpQueryHeaders(s->request,+ WINHTTP_QUERY_LOCATION,+ WINHTTP_HEADER_NAME_BY_INDEX,+ location,+ &location_length,+ WINHTTP_NO_HEADER_INDEX)) {+ giterr_set(GITERR_OS, "Failed to read Location header");+ git__free(location);+ return -1;+ }++ /* Compare the Location header with the request URI */+ redirect_cmp = wcscmp(location, s->request_uri);+ git__free(location);++ if (!redirect_cmp) {+ /* Replay the request */+ WinHttpCloseHandle(s->request);+ s->request = NULL;+ s->sent_request = 0;++ goto replay;+ }+ }+ }++ /* Handle authentication failures */+ if (HTTP_STATUS_DENIED == status_code &&+ get_verb == s->verb && t->owner->cred_acquire_cb) {+ int allowed_types;++ if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0)+ return -1;++ if (allowed_types &&+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {++ if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->user_from_url, allowed_types, t->owner->cred_acquire_payload) < 0)+ return -1;++ assert(t->cred);++ WinHttpCloseHandle(s->request);+ s->request = NULL;+ s->sent_request = 0;++ /* Successfully acquired a credential */+ goto replay;+ }+ }++ if (HTTP_STATUS_OK != status_code) {+ giterr_set(GITERR_NET, "Request failed with status code: %d", status_code);+ return -1;+ }++ /* Verify that we got the correct content-type back */+ if (post_verb == s->verb)+ snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);+ else+ snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);++ git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8);+ content_type_length = sizeof(content_type);++ if (!WinHttpQueryHeaders(s->request,+ WINHTTP_QUERY_CONTENT_TYPE,+ WINHTTP_HEADER_NAME_BY_INDEX,+ &content_type, &content_type_length,+ WINHTTP_NO_HEADER_INDEX)) {+ giterr_set(GITERR_OS, "Failed to retrieve response content-type");+ return -1;+ }++ if (wcscmp(expected_content_type, content_type)) {+ giterr_set(GITERR_NET, "Received unexpected content-type");+ return -1;+ }++ s->received_response = 1;+ }++ if (!WinHttpReadData(s->request,+ (LPVOID)buffer,+ (DWORD)buf_size,+ &dw_bytes_read))+ {+ giterr_set(GITERR_OS, "Failed to read data");+ return -1;+ }++ *bytes_read = dw_bytes_read;++ return 0;+}++static int winhttp_stream_write_single(+ git_smart_subtransport_stream *stream,+ const char *buffer,+ size_t len)+{+ winhttp_stream *s = (winhttp_stream *)stream;+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);+ DWORD bytes_written;++ if (!s->request && winhttp_stream_connect(s) < 0)+ return -1;++ /* This implementation of write permits only a single call. */+ if (s->sent_request) {+ giterr_set(GITERR_NET, "Subtransport configured for only one write");+ return -1;+ }++ if (!WinHttpSendRequest(s->request,+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,+ WINHTTP_NO_REQUEST_DATA, 0,+ (DWORD)len, 0)) {+ giterr_set(GITERR_OS, "Failed to send request");+ return -1;+ }++ s->sent_request = 1;++ if (!WinHttpWriteData(s->request,+ (LPCVOID)buffer,+ (DWORD)len,+ &bytes_written)) {+ giterr_set(GITERR_OS, "Failed to write data");+ return -1;+ }++ assert((DWORD)len == bytes_written);++ return 0;+}++static int put_uuid_string(LPWSTR buffer, size_t buffer_len_cch)+{+ UUID uuid;+ RPC_STATUS status = UuidCreate(&uuid);+ HRESULT result;++ if (RPC_S_OK != status &&+ RPC_S_UUID_LOCAL_ONLY != status &&+ RPC_S_UUID_NO_ADDRESS != status) {+ giterr_set(GITERR_NET, "Unable to generate name for temp file");+ return -1;+ }++ if (buffer_len_cch < UUID_LENGTH_CCH + 1) {+ giterr_set(GITERR_NET, "Buffer too small for name of temp file");+ return -1;+ }++ result = StringCbPrintfW(+ buffer, buffer_len_cch,+ L"%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x",+ uuid.Data1, uuid.Data2, uuid.Data3,+ uuid.Data4[0], uuid.Data4[1], uuid.Data4[2], uuid.Data4[3],+ uuid.Data4[4], uuid.Data4[5], uuid.Data4[6], uuid.Data4[7]);++ if (FAILED(result)) {+ giterr_set(GITERR_OS, "Unable to generate name for temp file");+ return -1;+ }++ return 0;+}++static int get_temp_file(LPWSTR buffer, DWORD buffer_len_cch)+{+ size_t len;++ if (!GetTempPathW(buffer_len_cch, buffer)) {+ giterr_set(GITERR_OS, "Failed to get temp path");+ return -1;+ }++ len = wcslen(buffer);++ if (buffer[len - 1] != '\\' && len < buffer_len_cch)+ buffer[len++] = '\\';++ if (put_uuid_string(&buffer[len], (size_t)buffer_len_cch - len) < 0)+ return -1;++ return 0;+}++static int winhttp_stream_write_buffered(+ git_smart_subtransport_stream *stream,+ const char *buffer,+ size_t len)+{+ winhttp_stream *s = (winhttp_stream *)stream;+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);+ DWORD bytes_written;++ if (!s->request && winhttp_stream_connect(s) < 0)+ return -1;++ /* Buffer the payload, using a temporary file so we delegate+ * memory management of the data to the operating system. */+ if (!s->post_body) {+ wchar_t temp_path[MAX_PATH + 1];++ if (get_temp_file(temp_path, MAX_PATH + 1) < 0)+ return -1;++ s->post_body = CreateFileW(temp_path,+ GENERIC_READ | GENERIC_WRITE,+ FILE_SHARE_DELETE, NULL,+ CREATE_NEW,+ FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_SEQUENTIAL_SCAN,+ NULL);++ if (INVALID_HANDLE_VALUE == s->post_body) {+ s->post_body = NULL;+ giterr_set(GITERR_OS, "Failed to create temporary file");+ return -1;+ }+ }++ if (!WriteFile(s->post_body, buffer, (DWORD)len, &bytes_written, NULL)) {+ giterr_set(GITERR_OS, "Failed to write to temporary file");+ return -1;+ }++ assert((DWORD)len == bytes_written);++ s->post_body_len += bytes_written;++ return 0;+}++static int winhttp_stream_write_chunked(+ git_smart_subtransport_stream *stream,+ const char *buffer,+ size_t len)+{+ winhttp_stream *s = (winhttp_stream *)stream;+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);++ if (!s->request && winhttp_stream_connect(s) < 0)+ return -1;++ if (!s->sent_request) {+ /* Send Transfer-Encoding: chunked header */+ if (!WinHttpAddRequestHeaders(s->request,+ transfer_encoding, (ULONG) -1L,+ WINHTTP_ADDREQ_FLAG_ADD)) {+ giterr_set(GITERR_OS, "Failed to add a header to the request");+ return -1;+ }++ if (!WinHttpSendRequest(s->request,+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,+ WINHTTP_NO_REQUEST_DATA, 0,+ WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) {+ giterr_set(GITERR_OS, "Failed to send request");+ return -1;+ }++ s->sent_request = 1;+ }++ if (len > CACHED_POST_BODY_BUF_SIZE) {+ /* Flush, if necessary */+ if (s->chunk_buffer_len > 0) {+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)+ return -1;++ s->chunk_buffer_len = 0;+ }++ /* Write chunk directly */+ if (write_chunk(s->request, buffer, len) < 0)+ return -1;+ }+ else {+ /* Append as much to the buffer as we can */+ int count = min(CACHED_POST_BODY_BUF_SIZE - s->chunk_buffer_len, (int)len);++ if (!s->chunk_buffer)+ s->chunk_buffer = git__malloc(CACHED_POST_BODY_BUF_SIZE);++ memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);+ s->chunk_buffer_len += count;+ buffer += count;+ len -= count;++ /* Is the buffer full? If so, then flush */+ if (CACHED_POST_BODY_BUF_SIZE == s->chunk_buffer_len) {+ if (write_chunk(s->request, s->chunk_buffer, s->chunk_buffer_len) < 0)+ return -1;++ s->chunk_buffer_len = 0;++ /* Is there any remaining data from the source? */+ if (len > 0) {+ memcpy(s->chunk_buffer, buffer, len);+ s->chunk_buffer_len = (unsigned int)len;+ }+ }+ }++ return 0;+}++static void winhttp_stream_free(git_smart_subtransport_stream *stream)+{+ winhttp_stream *s = (winhttp_stream *)stream;++ if (s->chunk_buffer) {+ git__free(s->chunk_buffer);+ s->chunk_buffer = NULL;+ }++ if (s->post_body) {+ CloseHandle(s->post_body);+ s->post_body = NULL;+ }++ if (s->request_uri) {+ git__free(s->request_uri);+ s->request_uri = NULL;+ }++ if (s->request) {+ WinHttpCloseHandle(s->request);+ s->request = NULL;+ }++ git__free(s);+}++static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream)+{+ winhttp_stream *s;++ if (!stream)+ return -1;++ s = git__calloc(sizeof(winhttp_stream), 1);+ GITERR_CHECK_ALLOC(s);++ s->parent.subtransport = &t->parent;+ s->parent.read = winhttp_stream_read;+ s->parent.write = winhttp_stream_write_single;+ s->parent.free = winhttp_stream_free;++ *stream = s;++ return 0;+}++static int winhttp_connect(+ winhttp_subtransport *t,+ const char *url)+{+ wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";+ wchar_t host[GIT_WIN_PATH];+ int32_t port;+ const char *default_port;+ int ret;++ if (!git__prefixcmp(url, prefix_http)) {+ url = url + strlen(prefix_http);+ default_port = "80";+ }++ if (!git__prefixcmp(url, prefix_https)) {+ url += strlen(prefix_https);+ default_port = "443";+ t->use_ssl = 1;+ }++ if ((ret = gitno_extract_url_parts(&t->host, &t->port, &t->user_from_url,+ &t->pass_from_url, url, default_port)) < 0)+ return ret;++ t->path = strchr(url, '/');++ /* Prepare port */+ if (git__strtol32(&port, t->port, NULL, 10) < 0)+ return -1;++ /* Prepare host */+ git__utf8_to_16(host, GIT_WIN_PATH, t->host);++ /* Establish session */+ t->session = WinHttpOpen(+ ua,+ WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,+ WINHTTP_NO_PROXY_NAME,+ WINHTTP_NO_PROXY_BYPASS,+ 0);++ if (!t->session) {+ giterr_set(GITERR_OS, "Failed to init WinHTTP");+ return -1;+ }+ + /* Establish connection */+ t->connection = WinHttpConnect(+ t->session,+ host,+ port,+ 0);++ if (!t->connection) {+ giterr_set(GITERR_OS, "Failed to connect to host");+ return -1;+ }++ return 0;+}++static int winhttp_uploadpack_ls(+ winhttp_subtransport *t,+ winhttp_stream *s)+{+ s->service = upload_pack_service;+ s->service_url = upload_pack_ls_service_url;+ s->verb = get_verb;++ return 0;+}++static int winhttp_uploadpack(+ winhttp_subtransport *t,+ winhttp_stream *s)+{+ s->service = upload_pack_service;+ s->service_url = upload_pack_service_url;+ s->verb = post_verb;++ return 0;+}++static int winhttp_receivepack_ls(+ winhttp_subtransport *t,+ winhttp_stream *s)+{+ s->service = receive_pack_service;+ s->service_url = receive_pack_ls_service_url;+ s->verb = get_verb;++ return 0;+}++static int winhttp_receivepack(+ winhttp_subtransport *t,+ winhttp_stream *s)+{+ /* WinHTTP only supports Transfer-Encoding: chunked+ * on Windows Vista (NT 6.0) and higher. */+ s->chunked = git_has_win32_version(6, 0);++ if (s->chunked)+ s->parent.write = winhttp_stream_write_chunked;+ else+ s->parent.write = winhttp_stream_write_buffered;++ s->service = receive_pack_service;+ s->service_url = receive_pack_service_url;+ s->verb = post_verb;++ return 0;+}++static int winhttp_action(+ git_smart_subtransport_stream **stream,+ git_smart_subtransport *subtransport,+ const char *url,+ git_smart_service_t action)+{+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;+ winhttp_stream *s;+ int ret = -1;++ if (!t->connection &&+ winhttp_connect(t, url) < 0)+ return -1;++ if (winhttp_stream_alloc(t, &s) < 0)+ return -1;++ if (!stream)+ return -1;++ switch (action)+ {+ case GIT_SERVICE_UPLOADPACK_LS:+ ret = winhttp_uploadpack_ls(t, s);+ break;++ case GIT_SERVICE_UPLOADPACK:+ ret = winhttp_uploadpack(t, s);+ break;++ case GIT_SERVICE_RECEIVEPACK_LS:+ ret = winhttp_receivepack_ls(t, s);+ break;++ case GIT_SERVICE_RECEIVEPACK:+ ret = winhttp_receivepack(t, s);+ break;++ default:+ assert(0);+ }++ if (!ret)+ *stream = &s->parent;++ return ret;+}++static int winhttp_close(git_smart_subtransport *subtransport)+{+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;+ int ret = 0;++ if (t->host) {+ git__free(t->host);+ t->host = NULL;+ }++ if (t->port) {+ git__free(t->port);+ t->port = NULL;+ }++ if (t->user_from_url) {+ git__free(t->user_from_url);+ t->user_from_url = NULL;+ }++ if (t->pass_from_url) {+ git__free(t->pass_from_url);+ t->pass_from_url = NULL;+ }++ if (t->cred) {+ t->cred->free(t->cred);+ t->cred = NULL;+ }++ if (t->url_cred) {+ t->url_cred->free(t->url_cred);+ t->url_cred = NULL;+ }++ if (t->connection) {+ if (!WinHttpCloseHandle(t->connection)) {+ giterr_set(GITERR_OS, "Unable to close connection");+ ret = -1;+ }++ t->connection = NULL;+ }++ if (t->session) {+ if (!WinHttpCloseHandle(t->session)) {+ giterr_set(GITERR_OS, "Unable to close session");+ ret = -1;+ }++ t->session = NULL;+ }++ return ret;+}++static void winhttp_free(git_smart_subtransport *subtransport)+{+ winhttp_subtransport *t = (winhttp_subtransport *)subtransport;++ winhttp_close(subtransport);++ git__free(t);+}++int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)+{+ winhttp_subtransport *t;++ if (!out)+ return -1;++ t = git__calloc(sizeof(winhttp_subtransport), 1);+ GITERR_CHECK_ALLOC(t);++ t->owner = (transport_smart *)owner;+ t->parent.action = winhttp_action;+ t->parent.close = winhttp_close;+ t->parent.free = winhttp_free;++ *out = (git_smart_subtransport *) t;+ return 0;+}++#endif /* GIT_WINHTTP */
libgit2/src/tree-cache.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -69,7 +69,7 @@ return NULL; } - if (end == NULL || end + 1 == '\0')+ if (end == NULL || *end + 1 == '\0') return tree; ptr = end + 1;@@ -104,7 +104,7 @@ tree->name[name_len] = '\0'; /* Blank-terminated ASCII decimal number of entries in this tree */- if (git__strtol32(&count, buffer, &buffer, 10) < 0 || count < -1)+ if (git__strtol32(&count, buffer, &buffer, 10) < 0) goto corrupted; tree->entries = count;
libgit2/src/tree-cache.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/tree.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -12,30 +12,90 @@ #include "git2/object.h" #define DEFAULT_TREE_SIZE 16-#define MAX_FILEMODE 0777777 #define MAX_FILEMODE_BYTES 6 -static int valid_attributes(const int attributes)+static bool valid_filemode(const int filemode) {- return attributes >= 0 && attributes <= MAX_FILEMODE;+ return (filemode == GIT_FILEMODE_TREE+ || filemode == GIT_FILEMODE_BLOB+ || filemode == GIT_FILEMODE_BLOB_EXECUTABLE+ || filemode == GIT_FILEMODE_LINK+ || filemode == GIT_FILEMODE_COMMIT); } +GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode)+{+ /* Tree bits set, but it's not a commit */+ if (filemode & GIT_FILEMODE_TREE && !(filemode & 0100000))+ return GIT_FILEMODE_TREE;++ /* If any of the x bits is set */+ if (filemode & 0111)+ return GIT_FILEMODE_BLOB_EXECUTABLE;++ /* 16XXXX means commit */+ if ((filemode & GIT_FILEMODE_COMMIT) == GIT_FILEMODE_COMMIT)+ return GIT_FILEMODE_COMMIT;++ /* 12XXXX means commit */+ if ((filemode & GIT_FILEMODE_LINK) == GIT_FILEMODE_LINK)+ return GIT_FILEMODE_LINK;++ /* Otherwise, return a blob */+ return GIT_FILEMODE_BLOB;+}+ static int valid_entry_name(const char *filename) {- return strlen(filename) > 0 && strchr(filename, '/') == NULL;+ return *filename != '\0' &&+ strchr(filename, '/') == NULL &&+ (*filename != '.' ||+ (strcmp(filename, ".") != 0 &&+ strcmp(filename, "..") != 0 &&+ strcmp(filename, DOT_GIT) != 0)); } static int entry_sort_cmp(const void *a, const void *b) {- const git_tree_entry *entry_a = (const git_tree_entry *)(a);- const git_tree_entry *entry_b = (const git_tree_entry *)(b);+ const git_tree_entry *e1 = (const git_tree_entry *)a;+ const git_tree_entry *e2 = (const git_tree_entry *)b; return git_path_cmp(- entry_a->filename, entry_a->filename_len, git_tree_entry__is_tree(entry_a),- entry_b->filename, entry_b->filename_len, git_tree_entry__is_tree(entry_b));+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),+ git__strncmp); } +int git_tree_entry_cmp(const git_tree_entry *e1, const git_tree_entry *e2)+{+ return entry_sort_cmp(e1, e2);+} +int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2)+{+ return git_path_cmp(+ e1->filename, e1->filename_len, git_tree_entry__is_tree(e1),+ e2->filename, e2->filename_len, git_tree_entry__is_tree(e2),+ git__strncasecmp);+}++static git_tree_entry *alloc_entry(const char *filename)+{+ git_tree_entry *entry = NULL;+ size_t filename_len = strlen(filename);++ entry = git__malloc(sizeof(git_tree_entry) + filename_len + 1);+ if (!entry)+ return NULL;++ memset(entry, 0x0, sizeof(git_tree_entry));+ memcpy(entry->filename, filename, filename_len);+ entry->filename[filename_len] = 0;+ entry->filename_len = filename_len;++ return entry;+}+ struct tree_key_search { const char *filename; size_t filename_len;@@ -76,76 +136,116 @@ * ambiguous because of folder vs file sorting, we look linearly * around the area for our target file. */-static int tree_key_search(git_vector *entries, const char *filename)+static int tree_key_search(+ size_t *at_pos, git_vector *entries, const char *filename, size_t filename_len) { struct tree_key_search ksearch; const git_tree_entry *entry;-- int homing, i;+ size_t homing, i; ksearch.filename = filename;- ksearch.filename_len = strlen(filename);+ ksearch.filename_len = filename_len; /* Initial homing search; find an entry on the tree with * the same prefix as the filename we're looking for */- homing = git_vector_bsearch2(entries, &homing_search_cmp, &ksearch);- if (homing < 0)- return homing;+ if (git_vector_bsearch2(&homing, entries, &homing_search_cmp, &ksearch) < 0)+ return GIT_ENOTFOUND; /* We found a common prefix. Look forward as long as * there are entries that share the common prefix */- for (i = homing; i < (int)entries->length; ++i) {+ for (i = homing; i < entries->length; ++i) { entry = entries->contents[i]; if (homing_search_cmp(&ksearch, entry) < 0) break; - if (strcmp(filename, entry->filename) == 0)- return i;+ if (entry->filename_len == filename_len &&+ memcmp(filename, entry->filename, filename_len) == 0) {+ if (at_pos)+ *at_pos = i;++ return 0;+ } } /* If we haven't found our filename yet, look backwards * too as long as we have entries with the same prefix */- for (i = homing - 1; i >= 0; --i) {- entry = entries->contents[i];+ if (homing > 0) {+ i = homing - 1; - if (homing_search_cmp(&ksearch, entry) > 0)- break;+ do {+ entry = entries->contents[i]; - if (strcmp(filename, entry->filename) == 0)- return i;+ if (homing_search_cmp(&ksearch, entry) > 0)+ break;++ if (entry->filename_len == filename_len &&+ memcmp(filename, entry->filename, filename_len) == 0) {+ if (at_pos)+ *at_pos = i;++ return 0;+ }+ } while (i-- > 0); } /* The filename doesn't exist at all */ return GIT_ENOTFOUND; } -void git_tree__free(git_tree *tree)+void git_tree_entry_free(git_tree_entry *entry) {- unsigned int i;+ if (entry == NULL)+ return; - for (i = 0; i < tree->entries.length; ++i) {- git_tree_entry *e;- e = git_vector_get(&tree->entries, i);+ git__free(entry);+} - git__free(e->filename);- git__free(e);- }+git_tree_entry *git_tree_entry_dup(const git_tree_entry *entry)+{+ size_t total_size;+ git_tree_entry *copy; + assert(entry);++ total_size = sizeof(git_tree_entry) + entry->filename_len + 1;++ copy = git__malloc(total_size);+ if (!copy)+ return NULL;++ memcpy(copy, entry, total_size);++ return copy;+}++void git_tree__free(git_tree *tree)+{+ size_t i;+ git_tree_entry *e;++ git_vector_foreach(&tree->entries, i, e)+ git_tree_entry_free(e);+ git_vector_free(&tree->entries); git__free(tree); } -const git_oid *git_tree_id(git_tree *c)+const git_oid *git_tree_id(const git_tree *t) {- return git_object_id((git_object *)c);+ return git_object_id((const git_object *)t); } -unsigned int git_tree_entry_attributes(const git_tree_entry *entry)+git_repository *git_tree_owner(const git_tree *t) {- return entry->attr;+ return git_object_owner((const git_object *)t); } +git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)+{+ return (git_filemode_t)entry->attr;+}+ const char *git_tree_entry_name(const git_tree_entry *entry) { assert(entry);@@ -179,36 +279,61 @@ return git_object_lookup(object_out, repo, &entry->oid, GIT_OBJ_ANY); } -const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename)+static const git_tree_entry *entry_fromname(+ git_tree *tree, const char *name, size_t name_len) {- int idx;-- assert(tree && filename);+ size_t idx; - idx = tree_key_search(&tree->entries, filename);- if (idx == GIT_ENOTFOUND)+ if (tree_key_search(&idx, &tree->entries, name, name_len) < 0) return NULL; return git_vector_get(&tree->entries, idx); } -const git_tree_entry *git_tree_entry_byindex(git_tree *tree, unsigned int idx)+const git_tree_entry *git_tree_entry_byname(+ git_tree *tree, const char *filename) {+ assert(tree && filename);+ return entry_fromname(tree, filename, strlen(filename));+}++const git_tree_entry *git_tree_entry_byindex(+ git_tree *tree, size_t idx)+{ assert(tree); return git_vector_get(&tree->entries, idx); } +const git_tree_entry *git_tree_entry_byoid(+ const git_tree *tree, const git_oid *oid)+{+ size_t i;+ const git_tree_entry *e;++ assert(tree);++ git_vector_foreach(&tree->entries, i, e) {+ if (memcmp(&e->oid.id, &oid->id, sizeof(oid->id)) == 0)+ return e;+ }++ return NULL;+}+ int git_tree__prefix_position(git_tree *tree, const char *path) { git_vector *entries = &tree->entries; struct tree_key_search ksearch;- unsigned int at_pos;+ size_t at_pos; + if (!path)+ return 0;+ ksearch.filename = path; ksearch.filename_len = strlen(path); /* Find tree entry with appropriate prefix */- git_vector_bsearch3(&at_pos, entries, &homing_search_cmp, &ksearch);+ git_vector_bsearch2(&at_pos, entries, &homing_search_cmp, &ksearch); for (; at_pos < entries->length; ++at_pos) { const git_tree_entry *entry = entries->contents[at_pos];@@ -222,10 +347,10 @@ break; } - return at_pos;+ return (int)at_pos; } -unsigned int git_tree_entrycount(git_tree *tree)+size_t git_tree_entrycount(const git_tree *tree) { assert(tree); return tree->entries.length;@@ -233,20 +358,16 @@ unsigned int git_treebuilder_entrycount(git_treebuilder *bld) {- int count = 0;- int i; assert(bld);- for (i = 0; i < bld->entries.length; ++i) {- git_tree_entry *entry = bld->entries.contents[i];- if (! entry->removed)- ++count;- }- return count;+ return (unsigned int)bld->entrycount; } -static int tree_error(const char *str)+static int tree_error(const char *str, const char *path) {- giterr_set(GITERR_TREE, "%s", str);+ if (path)+ giterr_set(GITERR_TREE, "%s - %s", str, path);+ else+ giterr_set(GITERR_TREE, "%s", str); return -1; } @@ -257,29 +378,32 @@ while (buffer < buffer_end) { git_tree_entry *entry;- int tmp;-- entry = git__calloc(1, sizeof(git_tree_entry));- GITERR_CHECK_ALLOC(entry);-- if (git_vector_insert(&tree->entries, entry) < 0)- return -1;+ int attr; - if (git__strtol32(&tmp, buffer, &buffer, 8) < 0 ||- !buffer || !valid_attributes(tmp))- return tree_error("Failed to parse tree. Can't parse attributes");+ if (git__strtol32(&attr, buffer, &buffer, 8) < 0 || !buffer)+ return tree_error("Failed to parse tree. Can't parse filemode", NULL); - entry->attr = tmp;+ attr = normalize_filemode(attr); /* make sure to normalize the filemode */ if (*buffer++ != ' ')- return tree_error("Failed to parse tree. Object is corrupted");+ return tree_error("Failed to parse tree. Object is corrupted", NULL); if (memchr(buffer, 0, buffer_end - buffer) == NULL)- return tree_error("Failed to parse tree. Object is corrupted");+ return tree_error("Failed to parse tree. Object is corrupted", NULL); - entry->filename = git__strdup(buffer);- entry->filename_len = strlen(buffer);+ /** Allocate the entry and store it in the entries vector */+ {+ entry = alloc_entry(buffer);+ GITERR_CHECK_ALLOC(entry); + if (git_vector_insert(&tree->entries, entry) < 0) {+ git__free(entry);+ return -1;+ }++ entry->attr = attr;+ }+ while (buffer < buffer_end && *buffer != 0) buffer++; @@ -298,14 +422,13 @@ return tree_parse_buffer(tree, (char *)obj->raw.data, (char *)obj->raw.data + obj->raw.len); } -static unsigned int find_next_dir(const char *dirname, git_index *index, unsigned int start)+static size_t find_next_dir(const char *dirname, git_index *index, size_t start) {- unsigned int i, entries = git_index_entrycount(index);- size_t dirlen;+ size_t dirlen, i, entries = git_index_entrycount(index); dirlen = strlen(dirname); for (i = start; i < entries; ++i) {- git_index_entry *entry = git_index_get(index, i);+ const git_index_entry *entry = git_index_get_byindex(index, i); if (strlen(entry->path) < dirlen || memcmp(entry->path, dirname, dirlen) || (dirlen > 0 && entry->path[dirlen] != '/')) {@@ -316,22 +439,29 @@ return i; } -static int append_entry(git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes)+static int append_entry(+ git_treebuilder *bld,+ const char *filename,+ const git_oid *id,+ git_filemode_t filemode) { git_tree_entry *entry; - entry = git__calloc(1, sizeof(git_tree_entry));- GITERR_CHECK_ALLOC(entry);+ if (!valid_entry_name(filename))+ return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); - entry->filename = git__strdup(filename);- entry->filename_len = strlen(entry->filename);+ entry = alloc_entry(filename);+ GITERR_CHECK_ALLOC(entry); git_oid_cpy(&entry->oid, id);- entry->attr = attributes;+ entry->attr = (uint16_t)filemode; - if (git_vector_insert(&bld->entries, entry) < 0)+ if (git_vector_insert(&bld->entries, entry) < 0) {+ git__free(entry); return -1;+ } + bld->entrycount++; return 0; } @@ -340,11 +470,10 @@ git_repository *repo, git_index *index, const char *dirname,- unsigned int start)+ size_t start) { git_treebuilder *bld = NULL;-- unsigned int i, entries = git_index_entrycount(index);+ size_t i, entries = git_index_entrycount(index); int error; size_t dirname_len = strlen(dirname); const git_tree_cache *cache;@@ -352,13 +481,11 @@ cache = git_tree_cache_get(index->tree, dirname); if (cache != NULL && cache->entries >= 0){ git_oid_cpy(oid, &cache->oid);- return find_next_dir(dirname, index, start);+ return (int)find_next_dir(dirname, index, start); } - error = git_treebuilder_create(&bld, NULL);- if (bld == NULL) {+ if ((error = git_treebuilder_create(&bld, NULL)) < 0 || bld == NULL) return -1;- } /* * This loop is unfortunate, but necessary. The index doesn't have@@ -366,8 +493,8 @@ * need to keep track of the current position. */ for (i = start; i < entries; ++i) {- git_index_entry *entry = git_index_get(index, i);- char *filename, *next_slash;+ const git_index_entry *entry = git_index_get_byindex(index, i);+ const char *filename, *next_slash; /* * If we've left our (sub)tree, exit the loop and return. The@@ -398,7 +525,8 @@ /* Write out the subtree */ written = write_tree(&sub_oid, repo, index, subdir, i); if (written < 0) {- tree_error("Failed to write subtree");+ tree_error("Failed to write subtree", subdir);+ git__free(subdir); goto on_error; } else { i = written - 1; /* -1 because of the loop increment */@@ -416,18 +544,15 @@ } else { last_comp = subdir; }+ error = append_entry(bld, last_comp, &sub_oid, S_IFDIR); git__free(subdir);- if (error < 0) {- tree_error("Failed to insert dir");+ if (error < 0) goto on_error;- } } else { error = append_entry(bld, filename, &entry->oid, entry->mode);- if (error < 0) {- tree_error("Failed to insert file");+ if (error < 0) goto on_error;- } } } @@ -435,43 +560,54 @@ goto on_error; git_treebuilder_free(bld);- return i;+ return (int)i; on_error: git_treebuilder_free(bld); return -1; } -int git_tree_create_fromindex(git_oid *oid, git_index *index)+int git_tree__write_index(+ git_oid *oid, git_index *index, git_repository *repo) { int ret;- git_repository *repo;+ bool old_ignore_case = false; - repo = (git_repository *)GIT_REFCOUNT_OWNER(index);+ assert(oid && index && repo); - if (repo == NULL)- return tree_error("Failed to create tree. "- "The index file is not backed up by an existing repository");+ if (git_index_has_conflicts(index)) {+ giterr_set(GITERR_INDEX,+ "Cannot create a tree from a not fully merged index.");+ return GIT_EUNMERGED;+ } if (index->tree != NULL && index->tree->entries >= 0) { git_oid_cpy(oid, &index->tree->oid); return 0; } - /* The tree cache didn't help us */+ /* The tree cache didn't help us; we'll have to write+ * out a tree. If the index is ignore_case, we must+ * make it case-sensitive for the duration of the tree-write+ * operation. */++ if (index->ignore_case) {+ old_ignore_case = true;+ git_index__set_ignore_case(index, false);+ }+ ret = write_tree(oid, repo, index, "", 0);- return ret < 0 ? ret : 0;-} -static void sort_entries(git_treebuilder *bld)-{- git_vector_sort(&bld->entries);+ if (old_ignore_case)+ git_index__set_ignore_case(index, true);++ return ret < 0 ? ret : 0; } int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) { git_treebuilder *bld;- unsigned int i, source_entries = DEFAULT_TREE_SIZE;+ size_t i, source_entries = DEFAULT_TREE_SIZE; assert(builder_p); @@ -485,10 +621,13 @@ goto on_error; if (source != NULL) {- for (i = 0; i < source->entries.length; ++i) {- git_tree_entry *entry_src = source->entries.contents[i];+ git_tree_entry *entry_src; - if (append_entry(bld, entry_src->filename, &entry_src->oid, entry_src->attr) < 0)+ git_vector_foreach(&source->entries, i, entry_src) {+ if (append_entry(+ bld, entry_src->filename,+ &entry_src->oid,+ entry_src->attr) < 0) goto on_error; } }@@ -501,42 +640,46 @@ return -1; } -int git_treebuilder_insert(git_tree_entry **entry_out, git_treebuilder *bld, const char *filename, const git_oid *id, unsigned int attributes)+int git_treebuilder_insert(+ const git_tree_entry **entry_out,+ git_treebuilder *bld,+ const char *filename,+ const git_oid *id,+ git_filemode_t filemode) { git_tree_entry *entry;- int pos;+ size_t pos; assert(bld && id && filename); - if (!valid_attributes(attributes))- return tree_error("Failed to insert entry. Invalid attributes");+ if (!valid_filemode(filemode))+ return tree_error("Failed to insert entry. Invalid filemode for file", filename); if (!valid_entry_name(filename))- return tree_error("Failed to insert entry. Invalid name for a tree entry");-- pos = tree_key_search(&bld->entries, filename);+ return tree_error("Failed to insert entry. Invalid name for a tree entry", filename); - if (pos >= 0) {+ if (!tree_key_search(&pos, &bld->entries, filename, strlen(filename))) { entry = git_vector_get(&bld->entries, pos);- if (entry->removed)+ if (entry->removed) { entry->removed = 0;+ bld->entrycount++;+ } } else {- entry = git__calloc(1, sizeof(git_tree_entry));+ entry = alloc_entry(filename); GITERR_CHECK_ALLOC(entry); - entry->filename = git__strdup(filename);- entry->filename_len = strlen(entry->filename);+ if (git_vector_insert(&bld->entries, entry) < 0) {+ git__free(entry);+ return -1;+ }++ bld->entrycount++; } git_oid_cpy(&entry->oid, id);- entry->attr = attributes;-- if (pos == GIT_ENOTFOUND) {- if (git_vector_insert(&bld->entries, entry) < 0)- return -1;- }+ entry->attr = filemode; - if (entry_out != NULL)+ if (entry_out) *entry_out = entry; return 0;@@ -544,13 +687,12 @@ static git_tree_entry *treebuilder_get(git_treebuilder *bld, const char *filename) {- int idx;+ size_t idx; git_tree_entry *entry; assert(bld && filename); - idx = tree_key_search(&bld->entries, filename);- if (idx < 0)+ if (tree_key_search(&idx, &bld->entries, filename, strlen(filename)) < 0) return NULL; entry = git_vector_get(&bld->entries, idx);@@ -570,27 +712,29 @@ git_tree_entry *remove_ptr = treebuilder_get(bld, filename); if (remove_ptr == NULL || remove_ptr->removed)- return tree_error("Failed to remove entry. File isn't in the tree");+ return tree_error("Failed to remove entry. File isn't in the tree", filename); remove_ptr->removed = 1;+ bld->entrycount--; return 0; } int git_treebuilder_write(git_oid *oid, git_repository *repo, git_treebuilder *bld) {- unsigned int i;+ int error = 0;+ size_t i; git_buf tree = GIT_BUF_INIT; git_odb *odb; assert(bld); - sort_entries(bld);+ git_vector_sort(&bld->entries); /* Grow the buffer beforehand to an estimated size */- git_buf_grow(&tree, bld->entries.length * 72);+ error = git_buf_grow(&tree, bld->entries.length * 72); - for (i = 0; i < bld->entries.length; ++i) {- git_tree_entry *entry = bld->entries.contents[i];+ for (i = 0; i < bld->entries.length && !error; ++i) {+ git_tree_entry *entry = git_vector_get(&bld->entries, i); if (entry->removed) continue;@@ -598,153 +742,154 @@ git_buf_printf(&tree, "%o ", entry->attr); git_buf_put(&tree, entry->filename, entry->filename_len + 1); git_buf_put(&tree, (char *)entry->oid.id, GIT_OID_RAWSZ);- } - if (git_buf_oom(&tree))- goto on_error;-- if (git_repository_odb__weakptr(&odb, repo) < 0)- goto on_error;-+ if (git_buf_oom(&tree))+ error = -1;+ } - if (git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE) < 0)- goto on_error;+ if (!error &&+ !(error = git_repository_odb__weakptr(&odb, repo)))+ error = git_odb_write(oid, odb, tree.ptr, tree.size, GIT_OBJ_TREE); git_buf_free(&tree);- return 0;--on_error:- git_buf_free(&tree);- return -1;+ return error; } -void git_treebuilder_filter(git_treebuilder *bld, int (*filter)(const git_tree_entry *, void *), void *payload)+void git_treebuilder_filter(+ git_treebuilder *bld,+ git_treebuilder_filter_cb filter,+ void *payload) {- unsigned int i;+ size_t i;+ git_tree_entry *entry; assert(bld && filter); - for (i = 0; i < bld->entries.length; ++i) {- git_tree_entry *entry = bld->entries.contents[i];- if (!entry->removed && filter(entry, payload))+ git_vector_foreach(&bld->entries, i, entry) {+ if (!entry->removed && filter(entry, payload)) { entry->removed = 1;+ bld->entrycount--;+ } } } void git_treebuilder_clear(git_treebuilder *bld) {- unsigned int i;+ size_t i;+ git_tree_entry *e;+ assert(bld); - for (i = 0; i < bld->entries.length; ++i) {- git_tree_entry *e = bld->entries.contents[i];- git__free(e->filename);- git__free(e);- }+ git_vector_foreach(&bld->entries, i, e)+ git_tree_entry_free(e); git_vector_clear(&bld->entries);+ bld->entrycount = 0; } void git_treebuilder_free(git_treebuilder *bld) {+ if (bld == NULL)+ return;+ git_treebuilder_clear(bld); git_vector_free(&bld->entries); git__free(bld); } -static int tree_frompath(- git_tree **parent_out,+static size_t subpath_len(const char *path)+{+ const char *slash_pos = strchr(path, '/');+ if (slash_pos == NULL)+ return strlen(path);++ return slash_pos - path;+}++int git_tree_entry_bypath(+ git_tree_entry **entry_out, git_tree *root,- git_buf *treeentry_path,- size_t offset)+ const char *path) {- char *slash_pos = NULL;- const git_tree_entry* entry; int error = 0; git_tree *subtree;-- if (!*(treeentry_path->ptr + offset)) {- giterr_set(GITERR_INVALID,- "Invalid relative path to a tree entry '%s'.", treeentry_path->ptr);- return -1;- }-- slash_pos = (char *)strchr(treeentry_path->ptr + offset, '/');+ const git_tree_entry *entry;+ size_t filename_len; - if (slash_pos == NULL)- return git_tree_lookup(- parent_out,- root->object.repo,- git_object_id((const git_object *)root)- );+ /* Find how long is the current path component (i.e.+ * the filename between two slashes */+ filename_len = subpath_len(path); - if (slash_pos == treeentry_path->ptr + offset) {- giterr_set(GITERR_INVALID,- "Invalid relative path to a tree entry '%s'.", treeentry_path->ptr);- return -1;+ if (filename_len == 0) {+ giterr_set(GITERR_TREE, "Invalid tree path given");+ return GIT_ENOTFOUND; } - *slash_pos = '\0';-- entry = git_tree_entry_byname(root, treeentry_path->ptr + offset);-- if (slash_pos != NULL)- *slash_pos = '/';+ entry = entry_fromname(root, path, filename_len); if (entry == NULL) { giterr_set(GITERR_TREE,- "No tree entry can be found from "- "the given tree and relative path '%s'.", treeentry_path->ptr);+ "The path '%s' does not exist in the given tree", path); return GIT_ENOTFOUND; } + switch (path[filename_len]) {+ case '/':+ /* If there are more components in the path...+ * then this entry *must* be a tree */+ if (!git_tree_entry__is_tree(entry)) {+ giterr_set(GITERR_TREE,+ "The path '%s' does not exist in the given tree", path);+ return GIT_ENOTFOUND;+ } + /* If there's only a slash left in the path, we + * return the current entry; otherwise, we keep+ * walking down the path */+ if (path[filename_len + 1] != '\0')+ break;++ case '\0':+ /* If there are no more components in the path, return+ * this entry */+ *entry_out = git_tree_entry_dup(entry);+ return 0;+ }+ if (git_tree_lookup(&subtree, root->object.repo, &entry->oid) < 0)- return error;+ return -1; - error = tree_frompath(- parent_out,+ error = git_tree_entry_bypath(+ entry_out, subtree,- treeentry_path,- (slash_pos - treeentry_path->ptr) + 1+ path + filename_len + 1 ); git_tree_free(subtree); return error; } -int git_tree_get_subtree(- git_tree **subtree,- git_tree *root,- const char *subtree_path)-{- int error;- git_buf buffer = GIT_BUF_INIT;-- assert(subtree && root && subtree_path);-- if ((error = git_buf_sets(&buffer, subtree_path)) == 0)- error = tree_frompath(subtree, root, &buffer, 0);-- git_buf_free(&buffer);-- return error;-}--static int tree_walk_post(- git_tree *tree,+static int tree_walk(+ const git_tree *tree, git_treewalk_cb callback, git_buf *path,- void *payload)+ void *payload,+ bool preorder) { int error = 0;- unsigned int i;-- for (i = 0; i < tree->entries.length; ++i) {- git_tree_entry *entry = tree->entries.contents[i];+ size_t i;+ const git_tree_entry *entry; - if (callback(path->ptr, entry, payload) < 0)- continue;+ git_vector_foreach(&tree->entries, i, entry) {+ if (preorder) {+ error = callback(path->ptr, entry, payload);+ if (error > 0)+ continue;+ if (error < 0) {+ giterr_clear();+ return GIT_EUSER;+ }+ } if (git_tree_entry__is_tree(entry)) { git_tree *subtree;@@ -761,35 +906,40 @@ if (git_buf_oom(path)) return -1; - if (tree_walk_post(subtree, callback, path, payload) < 0)- return -1;+ error = tree_walk(subtree, callback, path, payload, preorder);+ if (error != 0)+ break; git_buf_truncate(path, path_len); git_tree_free(subtree); }++ if (!preorder && callback(path->ptr, entry, payload) < 0) {+ giterr_clear();+ error = GIT_EUSER;+ break;+ } } - return 0;+ return error; } -int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload)+int git_tree_walk(+ const git_tree *tree,+ git_treewalk_mode mode,+ git_treewalk_cb callback,+ void *payload) { int error = 0; git_buf root_path = GIT_BUF_INIT; - switch (mode) {- case GIT_TREEWALK_POST:- error = tree_walk_post(tree, callback, &root_path, payload);- break;-- case GIT_TREEWALK_PRE:- tree_error("Preorder tree walking is still not implemented");- return -1;-- default:- giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk");- return -1;+ if (mode != GIT_TREEWALK_POST && mode != GIT_TREEWALK_PRE) {+ giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk");+ return -1; }++ error = tree_walk(+ tree, callback, &root_path, payload, (mode == GIT_TREEWALK_PRE)); git_buf_free(&root_path);
libgit2/src/tree.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -13,11 +13,11 @@ #include "vector.h" struct git_tree_entry {- unsigned int attr;- char *filename;+ uint16_t removed;+ uint16_t attr; git_oid oid; size_t filename_len;- int removed;+ char filename[1]; }; struct git_tree {@@ -27,14 +27,16 @@ struct git_treebuilder { git_vector entries;+ size_t entrycount; /* vector may contain "removed" entries */ }; - GIT_INLINE(bool) git_tree_entry__is_tree(const struct git_tree_entry *e) { return (S_ISDIR(e->attr) && !S_ISGITLINK(e->attr)); } +extern int git_tree_entry_icmp(const git_tree_entry *e1, const git_tree_entry *e2);+ void git_tree__free(git_tree *tree); int git_tree__parse(git_tree *tree, git_odb_object *obj); @@ -47,5 +49,16 @@ */ int git_tree__prefix_position(git_tree *tree, const char *prefix); ++/**+ * Write a tree to the given repository+ */+int git_tree__write_index(+ git_oid *oid, git_index *index, git_repository *repo);++/**+ * Obsolete mode kept for compatibility reasons+ */+#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664 #endif
libgit2/src/tsort.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -23,9 +23,8 @@ # define MIN(x,y) (((x) < (y) ? (x) : (y))) #endif -typedef int (*cmp_ptr_t)(const void *, const void *);--static int binsearch(void **dst, const void *x, size_t size, cmp_ptr_t cmp)+static int binsearch(+ void **dst, const void *x, size_t size, git__sort_r_cmp cmp, void *payload) { int l, c, r; void *lx, *cx;@@ -38,12 +37,12 @@ lx = dst[l]; /* check for beginning conditions */- if (cmp(x, lx) < 0)+ if (cmp(x, lx, payload) < 0) return 0; - else if (cmp(x, lx) == 0) {+ else if (cmp(x, lx, payload) == 0) { int i = 1;- while (cmp(x, dst[i]) == 0)+ while (cmp(x, dst[i], payload) == 0) i++; return i; }@@ -51,7 +50,7 @@ /* guaranteed not to be >= rx */ cx = dst[c]; while (1) {- const int val = cmp(x, cx);+ const int val = cmp(x, cx, payload); if (val < 0) { if (c - l <= 1) return c; r = c;@@ -62,7 +61,7 @@ } else { do { cx = dst[++c];- } while (cmp(x, cx) == 0);+ } while (cmp(x, cx, payload) == 0); return c; } c = l + ((r - l) >> 1);@@ -71,7 +70,8 @@ } /* Binary insertion sort, but knowing that the first "start" entries are sorted. Used in timsort. */-static void bisort(void **dst, size_t start, size_t size, cmp_ptr_t cmp)+static void bisort(+ void **dst, size_t start, size_t size, git__sort_r_cmp cmp, void *payload) { size_t i; void *x;@@ -80,12 +80,12 @@ for (i = start; i < size; i++) { int j; /* If this entry is already correct, just move along */- if (cmp(dst[i - 1], dst[i]) <= 0)+ if (cmp(dst[i - 1], dst[i], payload) <= 0) continue; /* Else we need to find the right place, shift everything over, and squeeze in */ x = dst[i];- location = binsearch(dst, x, i, cmp);+ location = binsearch(dst, x, i, cmp, payload); for (j = (int)i - 1; j >= location; j--) { dst[j + 1] = dst[j]; }@@ -102,7 +102,8 @@ struct tsort_store { size_t alloc;- cmp_ptr_t cmp;+ git__sort_r_cmp cmp;+ void *payload; void **storage; }; @@ -118,7 +119,8 @@ } } -static ssize_t count_run(void **dst, ssize_t start, ssize_t size, struct tsort_store *store)+static ssize_t count_run(+ void **dst, ssize_t start, ssize_t size, struct tsort_store *store) { ssize_t curr = start + 2; @@ -126,7 +128,7 @@ return 1; if (start >= size - 2) {- if (store->cmp(dst[size - 2], dst[size - 1]) > 0) {+ if (store->cmp(dst[size - 2], dst[size - 1], store->payload) > 0) { void *tmp = dst[size - 1]; dst[size - 1] = dst[size - 2]; dst[size - 2] = tmp;@@ -135,13 +137,15 @@ return 2; } - if (store->cmp(dst[start], dst[start + 1]) <= 0) {- while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) <= 0)+ if (store->cmp(dst[start], dst[start + 1], store->payload) <= 0) {+ while (curr < size - 1 &&+ store->cmp(dst[curr - 1], dst[curr], store->payload) <= 0) curr++; return curr - start; } else {- while (curr < size - 1 && store->cmp(dst[curr - 1], dst[curr]) > 0)+ while (curr < size - 1 &&+ store->cmp(dst[curr - 1], dst[curr], store->payload) > 0) curr++; /* reverse in-place */@@ -219,7 +223,7 @@ for (k = curr; k < curr + A + B; k++) { if ((i < A) && (j < curr + A + B)) {- if (store->cmp(storage[i], dst[j]) <= 0)+ if (store->cmp(storage[i], dst[j], store->payload) <= 0) dst[k] = storage[i++]; else dst[k] = dst[j++];@@ -235,7 +239,7 @@ for (k = curr + A + B - 1; k >= curr; k--) { if ((i >= 0) && (j >= curr)) {- if (store->cmp(dst[j], storage[i]) > 0)+ if (store->cmp(dst[j], storage[i], store->payload) > 0) dst[k] = dst[j--]; else dst[k] = storage[i--];@@ -307,7 +311,7 @@ if (run < minrun) run = minrun;\ if (run > (ssize_t)size - curr) run = size - curr;\ if (run > len) {\- bisort(&dst[curr], len, run, cmp);\+ bisort(&dst[curr], len, run, cmp, payload);\ len = run;\ }\ run_stack[stack_curr].start = curr;\@@ -329,7 +333,8 @@ }\ while (0) -void git__tsort(void **dst, size_t size, cmp_ptr_t cmp)+void git__tsort_r(+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload) { struct tsort_store _store, *store = &_store; struct tsort_run run_stack[128];@@ -340,7 +345,7 @@ ssize_t minrun; if (size < 64) {- bisort(dst, 1, size, cmp);+ bisort(dst, 1, size, cmp, payload); return; } @@ -351,6 +356,7 @@ store->alloc = 0; store->storage = NULL; store->cmp = cmp;+ store->payload = payload; PUSH_NEXT(); PUSH_NEXT();@@ -364,4 +370,14 @@ PUSH_NEXT(); }+}++static int tsort_r_cmp(const void *a, const void *b, void *payload)+{+ return ((git__tsort_cmp)payload)(a, b);+}++void git__tsort(void **dst, size_t size, git__tsort_cmp cmp)+{+ git__tsort_r(dst, size, tsort_r_cmp, cmp); }
libgit2/src/unix/map.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -31,8 +31,11 @@ mflag = MAP_SHARED; else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) mflag = MAP_PRIVATE;+ else+ mflag = MAP_SHARED; out->data = mmap(NULL, len, mprot, mflag, fd, offset);+ if (!out->data || out->data == MAP_FAILED) { giterr_set(GITERR_OS, "Failed to mmap. Could not write data"); return -1;@@ -47,6 +50,7 @@ { assert(map != NULL); munmap(map->data, map->len);+ return 0; }
libgit2/src/unix/posix.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,25 +7,29 @@ #ifndef INCLUDE_posix__w32_h__ #define INCLUDE_posix__w32_h__ -#ifndef __sun-# include <fnmatch.h>-# define p_fnmatch(p, s, f) fnmatch(p, s, f)-#else-# include "compat/fnmatch.h"-#endif- #include <stdio.h>+#include <sys/param.h> #define p_lstat(p,b) lstat(p,b) #define p_readlink(a, b, c) readlink(a, b, c)+#define p_symlink(o,n) symlink(o, n) #define p_link(o,n) link(o, n) #define p_unlink(p) unlink(p) #define p_mkdir(p,m) mkdir(p, m) #define p_fsync(fd) fsync(fd)-#define p_realpath(p, po) realpath(p, po)++/* The OpenBSD realpath function behaves differently */+#if !defined(__OpenBSD__)+# define p_realpath(p, po) realpath(p, po)+#endif+ #define p_vsnprintf(b, c, f, a) vsnprintf(b, c, f, a) #define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__) #define p_mkstemp(p) mkstemp(p) #define p_setenv(n,v,o) setenv(n,v,o)+#define p_inet_pton(a, b, c) inet_pton(a, b, c)++/* see win32/posix.h for explanation about why this exists */+#define p_lstat_posixly(p,b) lstat(p,b) #endif
+ libgit2/src/unix/realpath.c view
@@ -0,0 +1,30 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#include <git2/common.h>++#ifdef __OpenBSD__++#include <limits.h>+#include <stdlib.h>+#include <fcntl.h>+#include <unistd.h>++char *p_realpath(const char *pathname, char *resolved)+{+ char *ret;++ if ((ret = realpath(pathname, resolved)) == NULL)+ return NULL;++ /* Figure out if the file exists */+ if (!access(ret, F_OK))+ return ret;++ return NULL;+}++#endif
libgit2/src/util.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,6 +10,7 @@ #include <stdio.h> #include <ctype.h> #include "posix.h"+#include "fileops.h" #ifdef _MSC_VER # include <Shlwapi.h>@@ -22,6 +23,91 @@ *rev = LIBGIT2_VER_REVISION; } +int git_libgit2_capabilities()+{+ return 0+#ifdef GIT_THREADS+ | GIT_CAP_THREADS+#endif+#if defined(GIT_SSL) || defined(GIT_WINHTTP)+ | GIT_CAP_HTTPS+#endif+ ;+}++/* Declarations for tuneable settings */+extern size_t git_mwindow__window_size;+extern size_t git_mwindow__mapped_limit;+extern size_t git_odb__cache_size;++static int config_level_to_futils_dir(int config_level)+{+ int val = -1;++ switch (config_level) {+ case GIT_CONFIG_LEVEL_SYSTEM: val = GIT_FUTILS_DIR_SYSTEM; break;+ case GIT_CONFIG_LEVEL_XDG: val = GIT_FUTILS_DIR_XDG; break;+ case GIT_CONFIG_LEVEL_GLOBAL: val = GIT_FUTILS_DIR_GLOBAL; break;+ default:+ giterr_set(+ GITERR_INVALID, "Invalid config path selector %d", config_level);+ }++ return val;+}++int git_libgit2_opts(int key, ...)+{+ int error = 0;+ va_list ap;++ va_start(ap, key);++ switch (key) {+ case GIT_OPT_SET_MWINDOW_SIZE:+ git_mwindow__window_size = va_arg(ap, size_t);+ break;++ case GIT_OPT_GET_MWINDOW_SIZE:+ *(va_arg(ap, size_t *)) = git_mwindow__window_size;+ break;++ case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT:+ git_mwindow__mapped_limit = va_arg(ap, size_t);+ break;++ case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT:+ *(va_arg(ap, size_t *)) = git_mwindow__mapped_limit;+ break;++ case GIT_OPT_GET_SEARCH_PATH:+ if ((error = config_level_to_futils_dir(va_arg(ap, int))) >= 0) {+ char *out = va_arg(ap, char *);+ size_t outlen = va_arg(ap, size_t);++ error = git_futils_dirs_get_str(out, outlen, error);+ }+ break;++ case GIT_OPT_SET_SEARCH_PATH:+ if ((error = config_level_to_futils_dir(va_arg(ap, int))) >= 0)+ error = git_futils_dirs_set(error, va_arg(ap, const char *));+ break;++ case GIT_OPT_GET_ODB_CACHE_SIZE:+ *(va_arg(ap, size_t *)) = git_odb__cache_size;+ break;++ case GIT_OPT_SET_ODB_CACHE_SIZE:+ git_odb__cache_size = va_arg(ap, size_t);+ break;+ }++ va_end(ap);++ return error;+}+ void git_strarray_free(git_strarray *array) { size_t i;@@ -29,6 +115,8 @@ git__free(array->strings[i]); git__free(array->strings);++ memset(array, 0, sizeof(*array)); } int git_strarray_copy(git_strarray *tgt, const git_strarray *src)@@ -46,8 +134,10 @@ GITERR_CHECK_ALLOC(tgt->strings); for (i = 0; i < src->count; ++i) {- tgt->strings[tgt->count] = git__strdup(src->strings[i]);+ if (!src->strings[i])+ continue; + tgt->strings[tgt->count] = git__strdup(src->strings[i]); if (!tgt->strings[tgt->count]) { git_strarray_free(tgt); memset(tgt, 0, sizeof(*tgt));@@ -162,6 +252,42 @@ return error; } +int git__strcmp(const char *a, const char *b)+{+ while (*a && *b && *a == *b)+ ++a, ++b;+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);+}++int git__strcasecmp(const char *a, const char *b)+{+ while (*a && *b && tolower(*a) == tolower(*b))+ ++a, ++b;+ return (tolower(*a) - tolower(*b));+}++int git__strncmp(const char *a, const char *b, size_t sz)+{+ while (sz && *a && *b && *a == *b)+ --sz, ++a, ++b;+ if (!sz)+ return 0;+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);+}++int git__strncasecmp(const char *a, const char *b, size_t sz)+{+ int al, bl;++ do {+ al = (unsigned char)tolower(*a);+ bl = (unsigned char)tolower(*b);+ ++a, ++b;+ } while (--sz && al && al == bl);++ return al - bl;+}+ void git__strntolower(char *str, size_t len) { size_t i;@@ -179,7 +305,7 @@ int git__prefixcmp(const char *str, const char *prefix) { for (;;) {- char p = *(prefix++), s;+ unsigned char p = *(prefix++), s; if (!p) return 0; if ((s = *(str++)) != p)@@ -187,6 +313,11 @@ } } +int git__prefixcmp_icase(const char *str, const char *prefix)+{+ return strncasecmp(str, prefix, strlen(prefix));+}+ int git__suffixcmp(const char *str, const char *suffix) { size_t a = strlen(str);@@ -221,6 +352,24 @@ return NULL; } +/* Similar to strtok, but does not collapse repeated tokens. */+char *git__strsep(char **end, const char *sep)+{+ char *start = *end, *ptr = *end;++ while (*ptr && !strchr(sep, *ptr))+ ++ptr;++ if (*ptr) {+ *end = ptr + 1;+ *ptr = '\0';++ return start;+ }++ return NULL;+}+ void git__hexdump(const char *buffer, size_t len) { static const size_t LINE_WIDTH = 16;@@ -367,6 +516,30 @@ * * Copyright (c) 1990 Regents of the University of California. * All rights reserved.+ * Redistribution and use in source and binary forms, with or without+ * modification, are permitted provided that the following conditions+ * are met:+ * 1. Redistributions of source code must retain the above copyright+ * notice, this list of conditions and the following disclaimer.+ * 2. Redistributions in binary form must reproduce the above copyright+ * notice, this list of conditions and the following disclaimer in the+ * documentation and/or other materials provided with the distribution.+ * 3. [rescinded 22 July 1999]+ * 4. Neither the name of the University nor the names of its contributors+ * may be used to endorse or promote products derived from this software+ * without specific prior written permission.+ *+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+ * SUCH DAMAGE. */ int git__bsearch( void **array,@@ -375,11 +548,11 @@ int (*compare)(const void *, const void *), size_t *position) {- unsigned int lim;+ size_t lim; int cmp = -1; void **part, **base = array; - for (lim = (unsigned int)array_len; lim != 0; lim >>= 1) {+ for (lim = array_len; lim != 0; lim >>= 1) { part = base + (lim >> 1); cmp = (*compare)(key, *part); if (cmp == 0) {@@ -395,12 +568,43 @@ if (position) *position = (base - array); - return (cmp == 0) ? 0 : -1;+ return (cmp == 0) ? 0 : GIT_ENOTFOUND; } +int git__bsearch_r(+ void **array,+ size_t array_len,+ const void *key,+ int (*compare_r)(const void *, const void *, void *),+ void *payload,+ size_t *position)+{+ size_t lim;+ int cmp = -1;+ void **part, **base = array;++ for (lim = array_len; lim != 0; lim >>= 1) {+ part = base + (lim >> 1);+ cmp = (*compare_r)(key, *part, payload);+ if (cmp == 0) {+ base = part;+ break;+ }+ if (cmp > 0) { /* key > p; take right partition */+ base = part + 1;+ lim--;+ } /* else take left partition */+ }++ if (position)+ *position = (base - array);++ return (cmp == 0) ? 0 : GIT_ENOTFOUND;+}+ /** * A strcmp wrapper- * + * * We don't want direct pointers to the CRT on Windows, we may * get stdcall conflicts. */@@ -415,12 +619,8 @@ int git__parse_bool(int *out, const char *value) { /* A missing value means true */- if (value == NULL) {- *out = 1;- return 0;- }-- if (!strcasecmp(value, "true") ||+ if (value == NULL ||+ !strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) { *out = 1;@@ -428,10 +628,82 @@ } if (!strcasecmp(value, "false") || !strcasecmp(value, "no") ||- !strcasecmp(value, "off")) {+ !strcasecmp(value, "off") ||+ value[0] == '\0') { *out = 0; return 0; } return -1;+}++size_t git__unescape(char *str)+{+ char *scan, *pos = str;++ for (scan = str; *scan; pos++, scan++) {+ if (*scan == '\\' && *(scan + 1) != '\0')+ scan++; /* skip '\' but include next char */+ if (pos != scan)+ *pos = *scan;+ }++ if (pos != scan) {+ *pos = '\0';+ }++ return (pos - str);+}++#if defined(GIT_WIN32) || defined(BSD)+typedef struct {+ git__sort_r_cmp cmp;+ void *payload;+} git__qsort_r_glue;++static int GIT_STDLIB_CALL git__qsort_r_glue_cmp(+ void *payload, const void *a, const void *b)+{+ git__qsort_r_glue *glue = payload;+ return glue->cmp(a, b, glue->payload);+}+#endif++void git__qsort_r(+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload)+{+#if defined(__MINGW32__) || defined(__OpenBSD__)+ git__insertsort_r(els, nel, elsize, NULL, cmp, payload);+#elif defined(GIT_WIN32)+ git__qsort_r_glue glue = { cmp, payload };+ qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue);+#elif defined(BSD)+ git__qsort_r_glue glue = { cmp, payload };+ qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp);+#else+ qsort_r(els, nel, elsize, cmp, payload);+#endif+}++void git__insertsort_r(+ void *els, size_t nel, size_t elsize, void *swapel,+ git__sort_r_cmp cmp, void *payload)+{+ uint8_t *base = els;+ uint8_t *end = base + nel * elsize;+ uint8_t *i, *j;+ bool freeswap = !swapel;++ if (freeswap)+ swapel = git__malloc(elsize);++ for (i = base + elsize; i < end; i += elsize)+ for (j = i; j > base && cmp(j, j - elsize, payload) < 0; j -= elsize) {+ memcpy(swapel, j, elsize);+ memcpy(j, j - elsize, elsize);+ memcpy(j - elsize, swapel, elsize);+ }++ if (freeswap)+ git__free(swapel); }
libgit2/src/util.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -13,6 +13,9 @@ #ifndef min # define min(a,b) ((a) < (b) ? (a) : (b)) #endif+#ifndef max+# define max(a,b) ((a) > (b) ? (a) : (b))+#endif /* * Custom memory allocation wrappers@@ -42,25 +45,31 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) {- size_t length;+ size_t length = 0; char *ptr; - length = strlen(str);- if (n < length)- length = n;+ while (length < n && str[length])+ ++length; - ptr = (char*)malloc(length + 1);- if (!ptr) {- giterr_set_oom();- return NULL;- }+ ptr = (char*)git__malloc(length + 1); - memcpy(ptr, str, length);+ if (length)+ memcpy(ptr, str, length);+ ptr[length] = '\0'; return ptr; } +/* NOTE: This doesn't do null or '\0' checking. Watch those boundaries! */+GIT_INLINE(char *) git__substrdup(const char *start, size_t n)+{+ char *ptr = (char*)git__malloc(n+1);+ memcpy(ptr, start, n);+ ptr[n] = '\0';+ return ptr;+}+ GIT_INLINE(void *) git__realloc(void *ptr, size_t size) { void *new_ptr = realloc(ptr, size);@@ -70,9 +79,21 @@ #define git__free(ptr) free(ptr) +#define STRCMP_CASESELECT(IGNORE_CASE, STR1, STR2) \+ ((IGNORE_CASE) ? strcasecmp((STR1), (STR2)) : strcmp((STR1), (STR2)))++#define CASESELECT(IGNORE_CASE, ICASE, CASE) \+ ((IGNORE_CASE) ? (ICASE) : (CASE))+ extern int git__prefixcmp(const char *str, const char *prefix);+extern int git__prefixcmp_icase(const char *str, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); +GIT_INLINE(int) git__signum(int val)+{+ return ((val > 0) - (val < 0));+}+ extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base); extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base); @@ -94,6 +115,7 @@ #endif extern char *git__strtok(char **end, const char *sep);+extern char *git__strsep(char **end, const char *sep); extern void git__strntolower(char *str, size_t len); extern void git__strtolower(char *str);@@ -105,22 +127,64 @@ return s; } -extern void git__tsort(void **dst, size_t size, int (*cmp)(const void *, const void *));+GIT_INLINE(const void *) git__memrchr(const void *s, int c, size_t n)+{+ const unsigned char *cp; + if (n != 0) {+ cp = (unsigned char *)s + n;+ do {+ if (*(--cp) == (unsigned char)c)+ return cp;+ } while (--n != 0);+ }++ return NULL;+}++typedef int (*git__tsort_cmp)(const void *a, const void *b);++extern void git__tsort(void **dst, size_t size, git__tsort_cmp cmp);++typedef int (*git__sort_r_cmp)(const void *a, const void *b, void *payload);++extern void git__tsort_r(+ void **dst, size_t size, git__sort_r_cmp cmp, void *payload);++extern void git__qsort_r(+ void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload);++extern void git__insertsort_r(+ void *els, size_t nel, size_t elsize, void *swapel,+ git__sort_r_cmp cmp, void *payload);+ /** * @param position If non-NULL, this will be set to the position where the * element is or would be inserted if not found.- * @return pos (>=0) if found or -1 if not found+ * @return 0 if found; GIT_ENOTFOUND if not found */ extern int git__bsearch( void **array, size_t array_len, const void *key,- int (*compare)(const void *, const void *),+ int (*compare)(const void *key, const void *element), size_t *position); +extern int git__bsearch_r(+ void **array,+ size_t array_len,+ const void *key,+ int (*compare_r)(const void *key, const void *element, void *payload),+ void *payload,+ size_t *position);+ extern int git__strcmp_cb(const void *a, const void *b); +extern int git__strcmp(const char *a, const char *b);+extern int git__strcasecmp(const char *a, const char *b);+extern int git__strncmp(const char *a, const char *b, size_t sz);+extern int git__strncasecmp(const char *a, const char *b, size_t sz);+ typedef struct { short refcount; void *owner;@@ -204,9 +268,14 @@ return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); } +GIT_INLINE(bool) git__isdigit(int c)+{+ return (c >= '0' && c <= '9');+}+ GIT_INLINE(bool) git__isspace(int c) {- return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v');+ return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */); } GIT_INLINE(bool) git__iswildcard(int c)@@ -222,5 +291,24 @@ * Valid values for false are: 'false', 'no', 'off' */ extern int git__parse_bool(int *out, const char *value);++/*+ * Parse a string into a value as a git_time_t.+ *+ * Sample valid input:+ * - "yesterday"+ * - "July 17, 2003"+ * - "2003-7-17 08:23"+ */+int git__date_parse(git_time_t *out, const char *date);++/*+ * Unescapes a string in-place.+ * + * Edge cases behavior:+ * - "jackie\" -> "jacky\"+ * - "chan\\" -> "chan\"+ */+extern size_t git__unescape(char *str); #endif /* INCLUDE_util_h__ */
libgit2/src/vector.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -9,18 +9,60 @@ #include "repository.h" #include "vector.h" -static const double resize_factor = 1.75;-static const unsigned int minimum_size = 8;+/* In elements, not bytes */+#define MIN_ALLOCSIZE 8 -static int resize_vector(git_vector *v)+GIT_INLINE(size_t) compute_new_size(git_vector *v) {- v->_alloc_size = ((unsigned int)(v->_alloc_size * resize_factor)) + 1;- if (v->_alloc_size < minimum_size)- v->_alloc_size = minimum_size;+ size_t new_size = v->_alloc_size; - v->contents = git__realloc(v->contents, v->_alloc_size * sizeof(void *));+ /* Use a resize factor of 1.5, which is quick to compute using integer+ * instructions and less than the golden ratio (1.618...) */+ if (new_size < MIN_ALLOCSIZE)+ new_size = MIN_ALLOCSIZE;+ else if (new_size <= (SIZE_MAX / 3) * 2)+ new_size += new_size / 2;+ else+ new_size = SIZE_MAX;++ return new_size;+}++GIT_INLINE(int) resize_vector(git_vector *v, size_t new_size)+{+ size_t new_bytes = new_size * sizeof(void *);+ void *new_contents;++ /* Check for overflow */+ if (new_bytes / sizeof(void *) != new_size)+ GITERR_CHECK_ALLOC(NULL);++ new_contents = git__realloc(v->contents, new_bytes);+ GITERR_CHECK_ALLOC(new_contents);++ v->_alloc_size = new_size;+ v->contents = new_contents;++ return 0;+}++int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp)+{+ size_t bytes;++ assert(v && src);++ bytes = src->length * sizeof(void *);++ v->_alloc_size = src->length;+ v->_cmp = cmp;+ v->length = src->length;+ v->sorted = src->sorted && cmp == src->_cmp;+ v->contents = git__malloc(bytes); GITERR_CHECK_ALLOC(v->contents); + memcpy(v->contents, src->contents, bytes);+ return 0; } @@ -35,25 +77,17 @@ v->_alloc_size = 0; } -int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp)+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) { assert(v); - memset(v, 0x0, sizeof(git_vector));-- if (initial_size == 0)- initial_size = minimum_size;-- v->_alloc_size = initial_size;+ v->_alloc_size = 0; v->_cmp = cmp;- v->length = 0; v->sorted = 1;-- v->contents = git__malloc(v->_alloc_size * sizeof(void *));- GITERR_CHECK_ALLOC(v->contents);+ v->contents = NULL; - return 0;+ return resize_vector(v, max(initial_size, MIN_ALLOCSIZE)); } int git_vector_insert(git_vector *v, void *element)@@ -61,7 +95,7 @@ assert(v); if (v->length >= v->_alloc_size &&- resize_vector(v) < 0)+ resize_vector(v, compute_new_size(v)) < 0) return -1; v->contents[v->length++] = element;@@ -82,26 +116,25 @@ git_vector_sort(v); if (v->length >= v->_alloc_size &&- resize_vector(v) < 0)+ resize_vector(v, compute_new_size(v)) < 0) return -1; /* If we find the element and have a duplicate handler callback, * invoke it. If it returns non-zero, then cancel insert, otherwise * proceed with normal insert. */- if (git__bsearch(v->contents, v->length, element, v->_cmp, &pos) >= 0 &&- on_dup != NULL &&- (result = on_dup(&v->contents[pos], element)) < 0)+ if (!git__bsearch(v->contents, v->length, element, v->_cmp, &pos) &&+ on_dup && (result = on_dup(&v->contents[pos], element)) < 0) return result; /* shift elements to the right */- if (pos < v->length) {+ if (pos < v->length) memmove(v->contents + pos + 1, v->contents + pos, (v->length - pos) * sizeof(void *));- } v->contents[pos] = element; v->length++;+ return 0; } @@ -109,47 +142,44 @@ { assert(v); - if (v->sorted || v->_cmp == NULL)+ if (v->sorted || !v->_cmp) return; git__tsort(v->contents, v->length, v->_cmp); v->sorted = 1; } -int git_vector_bsearch3(- unsigned int *at_pos,+int git_vector_bsearch2(+ size_t *at_pos, git_vector *v, git_vector_cmp key_lookup, const void *key) {- int rval;- size_t pos;- assert(v && key && key_lookup); /* need comparison function to sort the vector */- assert(v->_cmp != NULL);+ if (!v->_cmp)+ return -1; git_vector_sort(v); - rval = git__bsearch(v->contents, v->length, key, key_lookup, &pos);-- if (at_pos != NULL)- *at_pos = (unsigned int)pos;-- return (rval >= 0) ? (int)pos : GIT_ENOTFOUND;+ return git__bsearch(v->contents, v->length, key, key_lookup, at_pos); } int git_vector_search2(- git_vector *v, git_vector_cmp key_lookup, const void *key)+ size_t *at_pos, const git_vector *v, git_vector_cmp key_lookup, const void *key) {- unsigned int i;+ size_t i; assert(v && key && key_lookup); for (i = 0; i < v->length; ++i) {- if (key_lookup(key, v->contents[i]) == 0)- return i;+ if (key_lookup(key, v->contents[i]) == 0) {+ if (at_pos)+ *at_pos = i;++ return 0;+ } } return GIT_ENOTFOUND;@@ -160,23 +190,26 @@ return (a == b) ? 0 : -1; } -int git_vector_search(git_vector *v, const void *entry)+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry) {- return git_vector_search2(v, v->_cmp ? v->_cmp : strict_comparison, entry);+ return git_vector_search2(at_pos, v, v->_cmp ? v->_cmp : strict_comparison, entry); } -int git_vector_remove(git_vector *v, unsigned int idx)+int git_vector_remove(git_vector *v, size_t idx) {- unsigned int i;+ size_t shift_count; assert(v); - if (idx >= v->length || v->length == 0)+ if (idx >= v->length) return GIT_ENOTFOUND; - for (i = idx; i < v->length - 1; ++i)- v->contents[i] = v->contents[i + 1];+ shift_count = v->length - idx - 1; + if (shift_count)+ memmove(&v->contents[idx], &v->contents[idx + 1],+ shift_count * sizeof(void *));+ v->length--; return 0; }@@ -190,7 +223,7 @@ void git_vector_uniq(git_vector *v) { git_vector_cmp cmp;- unsigned int i, j;+ size_t i, j; if (v->length <= 1) return;@@ -207,6 +240,21 @@ v->length -= j - i - 1; } +void git_vector_remove_matching(+ git_vector *v, int (*match)(const git_vector *v, size_t idx))+{+ size_t i, j;++ for (i = 0, j = 0; j < v->length; ++j) {+ v->contents[i] = v->contents[j];++ if (!match(v, i))+ i++;+ }++ v->length = i;+}+ void git_vector_clear(git_vector *v) { assert(v);@@ -218,10 +266,41 @@ { git_vector t; - if (!a || !b || a == b)- return;+ assert(a && b); - memcpy(&t, a, sizeof(t));- memcpy(a, b, sizeof(t));- memcpy(b, &t, sizeof(t));+ if (a != b) {+ memcpy(&t, a, sizeof(t));+ memcpy(a, b, sizeof(t));+ memcpy(b, &t, sizeof(t));+ }+}++int git_vector_resize_to(git_vector *v, size_t new_length)+{+ if (new_length <= v->length)+ return 0;++ if (new_length > v->_alloc_size &&+ resize_vector(v, new_length) < 0)+ return -1;++ memset(&v->contents[v->length], 0,+ sizeof(void *) * (new_length - v->length));++ v->length = new_length;++ return 0;+}++int git_vector_set(void **old, git_vector *v, size_t position, void *value)+{+ if (git_vector_resize_to(v, position + 1) < 0)+ return -1;++ if (old != NULL)+ *old = v->contents[position];++ v->contents[position] = value;++ return 0; }
libgit2/src/vector.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -12,52 +12,50 @@ typedef int (*git_vector_cmp)(const void *, const void *); typedef struct git_vector {- unsigned int _alloc_size;+ size_t _alloc_size; git_vector_cmp _cmp; void **contents;- unsigned int length;+ size_t length; int sorted; } git_vector; #define GIT_VECTOR_INIT {0} -int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp);+int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v);+int git_vector_dup(git_vector *v, const git_vector *src, git_vector_cmp cmp); void git_vector_swap(git_vector *a, git_vector *b); void git_vector_sort(git_vector *v); -int git_vector_search(git_vector *v, const void *entry);-int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key);--int git_vector_bsearch3(- unsigned int *at_pos, git_vector *v, git_vector_cmp cmp, const void *key);+/** Linear search for matching entry using internal comparison function */+int git_vector_search(size_t *at_pos, const git_vector *v, const void *entry); -GIT_INLINE(int) git_vector_bsearch(git_vector *v, const void *key)-{- return git_vector_bsearch3(NULL, v, v->_cmp, key);-}+/** Linear search for matching entry using explicit comparison function */+int git_vector_search2(size_t *at_pos, const git_vector *v, git_vector_cmp cmp, const void *key); -GIT_INLINE(int) git_vector_bsearch2(- git_vector *v, git_vector_cmp cmp, const void *key)-{- return git_vector_bsearch3(NULL, v, cmp, key);-}+/**+ * Binary search for matching entry using explicit comparison function that+ * returns position where item would go if not found.+ */+int git_vector_bsearch2(+ size_t *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); -GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position)+/** Binary search for matching entry using internal comparison function */+GIT_INLINE(int) git_vector_bsearch(size_t *at_pos, git_vector *v, const void *key) {- return (position < v->length) ? v->contents[position] : NULL;+ return git_vector_bsearch2(at_pos, v, v->_cmp, key); } -GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int position)+GIT_INLINE(void *) git_vector_get(const git_vector *v, size_t position) { return (position < v->length) ? v->contents[position] : NULL; } #define GIT_VECTOR_GET(V,I) ((I) < (V)->length ? (V)->contents[(I)] : NULL) -GIT_INLINE(void *) git_vector_last(git_vector *v)+GIT_INLINE(void *) git_vector_last(const git_vector *v) { return (v->length > 0) ? git_vector_get(v, v->length - 1) : NULL; }@@ -66,13 +64,18 @@ for ((iter) = 0; (iter) < (v)->length && ((elem) = (v)->contents[(iter)], 1); (iter)++ ) #define git_vector_rforeach(v, iter, elem) \- for ((iter) = (v)->length; (iter) > 0 && ((elem) = (v)->contents[(iter)-1], 1); (iter)-- )+ for ((iter) = (v)->length - 1; (iter) < SIZE_MAX && ((elem) = (v)->contents[(iter)], 1); (iter)-- ) int git_vector_insert(git_vector *v, void *element); int git_vector_insert_sorted(git_vector *v, void *element, int (*on_dup)(void **old, void *new));-int git_vector_remove(git_vector *v, unsigned int idx);+int git_vector_remove(git_vector *v, size_t idx); void git_vector_pop(git_vector *v); void git_vector_uniq(git_vector *v);+void git_vector_remove_matching(+ git_vector *v, int (*match)(const git_vector *v, size_t idx));++int git_vector_resize_to(git_vector *v, size_t new_length);+int git_vector_set(void **old, git_vector *v, size_t position, void *value); #endif
libgit2/src/win32/dir.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,7 +7,6 @@ #define GIT__WIN32_NO_WRAP_DIR #include "dir.h" #include "utf-conv.h"-#include "git2/windows.h" static int init_filter(char *filter, size_t n, const char *dir) {@@ -26,8 +25,8 @@ git__DIR *git__opendir(const char *dir) {- char filter[4096];- wchar_t* filter_w = NULL;+ char filter[GIT_WIN_PATH];+ wchar_t filter_w[GIT_WIN_PATH]; git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir))@@ -41,12 +40,8 @@ if (!new->dir) goto fail; - filter_w = gitwin_to_utf16(filter);- if (!filter_w)- goto fail;-+ git__utf8_to_16(filter_w, GIT_WIN_PATH, filter); new->h = FindFirstFileW(filter_w, &new->f);- git__free(filter_w); if (new->h == INVALID_HANDLE_VALUE) { giterr_set(GITERR_OS, "Could not open directory '%s'", dir);@@ -85,16 +80,9 @@ if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) return -1; + git__utf16_to_8(entry->d_name, d->f.cFileName); entry->d_ino = 0; - if (WideCharToMultiByte(- gitwin_get_codepage(), 0, d->f.cFileName, -1,- entry->d_name, GIT_PATH_MAX, NULL, NULL) == 0)- {- giterr_set(GITERR_OS, "Could not convert filename to UTF-8");- return -1;- }- *result = entry; if (is_dir != NULL)@@ -113,8 +101,8 @@ void git__rewinddir(git__DIR *d) {- char filter[4096];- wchar_t* filter_w;+ char filter[GIT_WIN_PATH];+ wchar_t filter_w[GIT_WIN_PATH]; if (!d) return;@@ -125,12 +113,11 @@ d->first = 0; } - if (!init_filter(filter, sizeof(filter), d->dir) ||- (filter_w = gitwin_to_utf16(filter)) == NULL)+ if (!init_filter(filter, sizeof(filter), d->dir)) return; + git__utf8_to_16(filter_w, GIT_WIN_PATH, filter); d->h = FindFirstFileW(filter_w, &d->f);- git__free(filter_w); if (d->h == INVALID_HANDLE_VALUE) giterr_set(GITERR_OS, "Could not open directory '%s'", d->dir);
libgit2/src/win32/dir.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
+ libgit2/src/win32/error.c view
@@ -0,0 +1,77 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "common.h"+#include "error.h"++#ifdef GIT_WINHTTP+# include <winhttp.h>+#endif++#define WC_ERR_INVALID_CHARS 0x80++char *git_win32_get_error_message(DWORD error_code)+{+ LPWSTR lpMsgBuf = NULL;+ HMODULE hModule = NULL;+ char *utf8_msg = NULL;+ int utf8_size;+ DWORD dwFlags =+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS;++ if (!error_code)+ return NULL;++#ifdef GIT_WINHTTP+ /* Errors raised by WinHTTP are not in the system resource table */+ if (error_code >= WINHTTP_ERROR_BASE &&+ error_code <= WINHTTP_ERROR_LAST)+ hModule = GetModuleHandleW(L"winhttp");+#endif++ GIT_UNUSED(hModule);++ if (hModule)+ dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;+ else+ dwFlags |= FORMAT_MESSAGE_FROM_SYSTEM;++ if (FormatMessageW(dwFlags, hModule, error_code,+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),+ (LPWSTR)&lpMsgBuf, 0, NULL)) {++ /* Invalid code point check supported on Vista+ only */+ if (git_has_win32_version(6, 0))+ dwFlags = WC_ERR_INVALID_CHARS;+ else+ dwFlags = 0;++ utf8_size = WideCharToMultiByte(CP_UTF8, dwFlags,+ lpMsgBuf, -1, NULL, 0, NULL, NULL);++ if (!utf8_size) {+ assert(0);+ goto on_error;+ }++ utf8_msg = git__malloc(utf8_size);++ if (!utf8_msg)+ goto on_error;++ if (!WideCharToMultiByte(CP_UTF8, dwFlags,+ lpMsgBuf, -1, utf8_msg, utf8_size, NULL, NULL)) {+ git__free(utf8_msg);+ goto on_error;+ }++on_error:+ LocalFree(lpMsgBuf);+ }++ return utf8_msg;+}
+ libgit2/src/win32/error.h view
@@ -0,0 +1,13 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#ifndef INCLUDE_git_win32_error_h__+#define INCLUDE_git_win32_error_h__++extern char *git_win32_get_error_message(DWORD error_code);++#endif
+ libgit2/src/win32/findfile.c view
@@ -0,0 +1,238 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#include "utf-conv.h"+#include "path.h"+#include "findfile.h"++#define REG_MSYSGIT_INSTALL_LOCAL L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"++#ifndef _WIN64+#define REG_MSYSGIT_INSTALL REG_MSYSGIT_INSTALL_LOCAL+#else+#define REG_MSYSGIT_INSTALL L"SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Git_is1"+#endif++int git_win32__expand_path(struct git_win32__path *s_root, const wchar_t *templ)+{+ s_root->len = ExpandEnvironmentStringsW(templ, s_root->path, MAX_PATH);+ return s_root->len ? 0 : -1;+}++static int win32_path_utf16_to_8(git_buf *path_utf8, const wchar_t *path_utf16)+{+ char temp_utf8[GIT_PATH_MAX];++ git__utf16_to_8(temp_utf8, path_utf16);+ git_path_mkposix(temp_utf8);++ return git_buf_sets(path_utf8, temp_utf8);+}++int git_win32__find_file(+ git_buf *path, const struct git_win32__path *root, const char *filename)+{+ size_t len, alloc_len;+ wchar_t *file_utf16 = NULL;++ if (!root || !filename || (len = strlen(filename)) == 0)+ return GIT_ENOTFOUND;++ /* allocate space for wchar_t path to file */+ alloc_len = root->len + len + 2;+ file_utf16 = git__calloc(alloc_len, sizeof(wchar_t));+ GITERR_CHECK_ALLOC(file_utf16);++ /* append root + '\\' + filename as wchar_t */+ memcpy(file_utf16, root->path, root->len * sizeof(wchar_t));++ if (*filename == '/' || *filename == '\\')+ filename++;++ git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename);++ /* check access */+ if (_waccess(file_utf16, F_OK) < 0) {+ git__free(file_utf16);+ return GIT_ENOTFOUND;+ }++ win32_path_utf16_to_8(path, file_utf16);+ git__free(file_utf16);++ return 0;+}++static wchar_t* win32_walkpath(wchar_t *path, wchar_t *buf, size_t buflen)+{+ wchar_t term, *base = path;++ assert(path && buf && buflen);++ term = (*path == L'"') ? *path++ : L';';++ for (buflen--; *path && *path != term && buflen; buflen--)+ *buf++ = *path++;++ *buf = L'\0'; /* reserved a byte via initial subtract */++ while (*path == term || *path == L';')+ path++;++ return (path != base) ? path : NULL;+}++static int win32_find_git_in_path(git_buf *buf, const wchar_t *gitexe)+{+ wchar_t *env = _wgetenv(L"PATH"), lastch;+ struct git_win32__path root;+ size_t gitexe_len = wcslen(gitexe);++ if (!env)+ return -1;++ while ((env = win32_walkpath(env, root.path, MAX_PATH-1)) && *root.path) {+ root.len = (DWORD)wcslen(root.path);+ lastch = root.path[root.len - 1];++ /* ensure trailing slash (MAX_PATH-1 to walkpath guarantees space) */+ if (lastch != L'/' && lastch != L'\\') {+ root.path[root.len++] = L'\\';+ root.path[root.len] = L'\0';+ }++ if (root.len + gitexe_len >= MAX_PATH)+ continue;+ wcscpy(&root.path[root.len], gitexe);++ if (_waccess(root.path, F_OK) == 0 && root.len > 5) {+ /* replace "bin\\" or "cmd\\" with "etc\\" */+ wcscpy(&root.path[root.len - 4], L"etc\\");++ win32_path_utf16_to_8(buf, root.path);+ return 0;+ }+ }++ return GIT_ENOTFOUND;+}++static int win32_find_git_in_registry(+ git_buf *buf, const HKEY hieve, const wchar_t *key)+{+ HKEY hKey;+ DWORD dwType = REG_SZ;+ struct git_win32__path path16;++ assert(buf);++ path16.len = 0;++ if (RegOpenKeyExW(hieve, key, 0, KEY_ALL_ACCESS, &hKey) == ERROR_SUCCESS) {+ if (RegQueryValueExW(hKey, L"InstallLocation", NULL, &dwType,+ (LPBYTE)&path16.path, &path16.len) == ERROR_SUCCESS)+ {+ /* InstallLocation points to the root of the git directory */++ if (path16.len + 4 > MAX_PATH) { /* 4 = wcslen(L"etc\\") */+ giterr_set(GITERR_OS, "Cannot locate git - path too long");+ return -1;+ }++ wcscat(path16.path, L"etc\\");+ path16.len += 4;++ win32_path_utf16_to_8(buf, path16.path);+ }++ RegCloseKey(hKey);+ }++ return path16.len ? 0 : GIT_ENOTFOUND;+}++static int win32_find_existing_dirs(+ git_buf *out, const wchar_t *tmpl[], char *temp[])+{+ struct git_win32__path path16;+ git_buf buf = GIT_BUF_INIT;++ git_buf_clear(out);++ for (; *tmpl != NULL; tmpl++) {+ if (!git_win32__expand_path(&path16, *tmpl) &&+ path16.path[0] != L'%' &&+ !_waccess(path16.path, F_OK))+ {+ win32_path_utf16_to_8(&buf, path16.path);++ if (buf.size)+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);+ }+ }++ git_buf_free(&buf);++ return (git_buf_oom(out) ? -1 : 0);+}++int git_win32__find_system_dirs(git_buf *out)+{+ git_buf buf = GIT_BUF_INIT;++ /* directories where git.exe & git.cmd are found */+ if (!win32_find_git_in_path(&buf, L"git.exe") && buf.size)+ git_buf_set(out, buf.ptr, buf.size);+ else+ git_buf_clear(out);++ if (!win32_find_git_in_path(&buf, L"git.cmd") && buf.size)+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);++ /* directories where git is installed according to registry */+ if (!win32_find_git_in_registry(+ &buf, HKEY_CURRENT_USER, REG_MSYSGIT_INSTALL_LOCAL) && buf.size)+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);++ if (!win32_find_git_in_registry(+ &buf, HKEY_LOCAL_MACHINE, REG_MSYSGIT_INSTALL) && buf.size)+ git_buf_join(out, GIT_PATH_LIST_SEPARATOR, out->ptr, buf.ptr);++ git_buf_free(&buf);++ return (git_buf_oom(out) ? -1 : 0);+}++int git_win32__find_global_dirs(git_buf *out)+{+ char *temp[3];+ static const wchar_t *global_tmpls[4] = {+ L"%HOME%\\",+ L"%HOMEDRIVE%%HOMEPATH%\\",+ L"%USERPROFILE%\\",+ NULL,+ };++ return win32_find_existing_dirs(out, global_tmpls, temp);+}++int git_win32__find_xdg_dirs(git_buf *out)+{+ char *temp[6];+ static const wchar_t *global_tmpls[7] = {+ L"%XDG_CONFIG_HOME%\\git",+ L"%APPDATA%\\git",+ L"%LOCALAPPDATA%\\git",+ L"%HOME%\\.config\\git",+ L"%HOMEDRIVE%%HOMEPATH%\\.config\\git",+ L"%USERPROFILE%\\.config\\git",+ NULL,+ };++ return win32_find_existing_dirs(out, global_tmpls, temp);+}+
+ libgit2/src/win32/findfile.h view
@@ -0,0 +1,27 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */++#ifndef INCLUDE_git_findfile_h__+#define INCLUDE_git_findfile_h__++struct git_win32__path {+ wchar_t path[MAX_PATH];+ DWORD len;+};++extern int git_win32__expand_path(+ struct git_win32__path *s_root, const wchar_t *templ);++extern int git_win32__find_file(+ git_buf *path, const struct git_win32__path *root, const char *filename);++extern int git_win32__find_system_dirs(git_buf *out);+extern int git_win32__find_global_dirs(git_buf *out);+extern int git_win32__find_xdg_dirs(git_buf *out);++#endif+
libgit2/src/win32/map.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/win32/mingw-compat.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.
libgit2/src/win32/msvc-compat.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -21,6 +21,7 @@ /* stat: file mode type testing macros */ # define _S_IFLNK 0120000 # define S_IFLNK _S_IFLNK+# define S_IXUSR 00100 # define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) # define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)@@ -36,6 +37,15 @@ /* MSVC doesn't define ssize_t at all */ typedef SSIZE_T ssize_t; +/* define snprintf using variadic macro support if available */+#if _MSC_VER >= 1400+# define snprintf(BUF, SZ, FMT, ...) _snprintf_s(BUF, SZ, _TRUNCATE, FMT, __VA_ARGS__)+#else+# define snprintf _snprintf #endif++#endif++#define GIT_STDLIB_CALL __cdecl #endif /* INCLUDE_msvc_compat__ */
libgit2/src/win32/posix.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -8,7 +8,6 @@ #define INCLUDE_posix__w32_h__ #include "common.h"-#include "compat/fnmatch.h" #include "utf-conv.h" GIT_INLINE(int) p_link(const char *old, const char *new)@@ -21,18 +20,16 @@ GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) {- wchar_t* buf = gitwin_to_utf16(path);- int ret = _wmkdir(buf);-+ wchar_t buf[GIT_WIN_PATH]; GIT_UNUSED(mode);-- git__free(buf);- return ret;+ git__utf8_to_16(buf, GIT_WIN_PATH, path);+ return _wmkdir(buf); } extern int p_unlink(const char *path); extern int p_lstat(const char *file_name, struct stat *buf); extern int p_readlink(const char *link, char *target, size_t target_len);+extern int p_symlink(const char *old, const char *new); extern int p_hide_directory__w32(const char *path); extern char *p_realpath(const char *orig_path, char *buffer); extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr);@@ -51,5 +48,14 @@ extern int p_rename(const char *from, const char *to); extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags);+extern int p_inet_pton(int af, const char* src, void* dst);++/* p_lstat is almost but not quite POSIX correct. Specifically, the use of+ * ENOTDIR is wrong, in that it does not mean precisely that a non-directory+ * entry was encountered. Making it correct is potentially expensive,+ * however, so this is a separate version of p_lstat to use when correct+ * POSIX ENOTDIR semantics is required.+ */+extern int p_lstat_posixly(const char *filename, struct stat *buf); #endif
libgit2/src/win32/posix_w32.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,23 +7,18 @@ #include "../posix.h" #include "path.h" #include "utf-conv.h"+#include "repository.h" #include <errno.h> #include <io.h> #include <fcntl.h>-+#include <ws2tcpip.h> int p_unlink(const char *path) {- int ret = 0;- wchar_t* buf;-- if ((buf = gitwin_to_utf16(path)) != NULL) {- _wchmod(buf, 0666);- ret = _wunlink(buf);- git__free(buf);- }-- return ret;+ wchar_t buf[GIT_WIN_PATH];+ git__utf8_to_16(buf, GIT_WIN_PATH, path);+ _wchmod(buf, 0666);+ return _wunlink(buf); } int p_fsync(int fd)@@ -57,16 +52,32 @@ return (time_t)winTime; } -static int do_lstat(const char *file_name, struct stat *buf)+#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')++static int do_lstat(+ const char *file_name, struct stat *buf, int posix_enotdir) { WIN32_FILE_ATTRIBUTE_DATA fdata;- wchar_t* fbuf = gitwin_to_utf16(file_name);- if (!fbuf)- return -1;+ wchar_t fbuf[GIT_WIN_PATH], lastch;+ int flen; + flen = git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name);++ /* truncate trailing slashes */+ for (; flen > 0; --flen) {+ lastch = fbuf[flen - 1];+ if (WIN32_IS_WSEP(lastch))+ fbuf[flen - 1] = L'\0';+ else if (lastch != L'\0')+ break;+ }+ if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) { int fMode = S_IREAD; + if (!buf)+ return 0;+ if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) fMode |= S_IFDIR; else@@ -89,53 +100,68 @@ buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); - git__free(fbuf);- return 0;- }+ /* Windows symlinks have zero file size, call readlink to determine+ * the length of the path pointed to, which we expect everywhere else+ */+ if (S_ISLNK(fMode)) {+ char target[GIT_WIN_PATH];+ int readlink_result; - git__free(fbuf);- return -1;-}+ readlink_result = p_readlink(file_name, target, GIT_WIN_PATH); -int p_lstat(const char *file_name, struct stat *buf)-{- int error;- size_t namelen;- char *alt_name;+ if (readlink_result == -1)+ return -1; - if (do_lstat(file_name, buf) == 0)+ buf->st_size = strlen(target);+ }+ return 0;+ } - /* if file_name ended in a '/', Windows returned ENOENT;- * try again without trailing slashes+ errno = ENOENT;++ /* We need POSIX behavior, then ENOTDIR must set when any of the folders in the+ * file path is a regular file,otherwise ENOENT must be set. */- namelen = strlen(file_name);- if (namelen && file_name[namelen-1] != '/')- return -1;+ if (posix_enotdir) {+ /* scan up path until we find an existing item */+ while (1) {+ /* remove last directory component */+ for (--flen; flen > 0 && !WIN32_IS_WSEP(fbuf[flen]); --flen); - while (namelen && file_name[namelen-1] == '/')- --namelen;+ if (flen <= 0)+ break; - if (!namelen)- return -1;+ fbuf[flen] = L'\0'; - alt_name = git__strndup(file_name, namelen);- if (!alt_name)- return -1;+ if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {+ if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))+ errno = ENOTDIR;+ break;+ }+ }+ } - error = do_lstat(alt_name, buf);+ return -1;+} - git__free(alt_name);- return error;+int p_lstat(const char *filename, struct stat *buf)+{+ return do_lstat(filename, buf, 0); } +int p_lstat_posixly(const char *filename, struct stat *buf)+{+ return do_lstat(filename, buf, 1);+}+ int p_readlink(const char *link, char *target, size_t target_len) { typedef DWORD (WINAPI *fpath_func)(HANDLE, LPWSTR, DWORD, DWORD); static fpath_func pGetFinalPath = NULL; HANDLE hFile; DWORD dwRet;- wchar_t* link_w;+ wchar_t link_w[GIT_WIN_PATH]; wchar_t* target_w; int error = 0; @@ -146,10 +172,10 @@ * it is not available in platforms older than Vista */ if (pGetFinalPath == NULL) {- HINSTANCE library = LoadLibrary("kernel32");+ HMODULE module = GetModuleHandle("kernel32"); - if (library != NULL)- pGetFinalPath = (fpath_func)GetProcAddress(library, "GetFinalPathNameByHandleW");+ if (module != NULL)+ pGetFinalPath = (fpath_func)GetProcAddress(module, "GetFinalPathNameByHandleW"); if (pGetFinalPath == NULL) { giterr_set(GITERR_OS,@@ -158,8 +184,7 @@ } } - link_w = gitwin_to_utf16(link);- GITERR_CHECK_ALLOC(link_w);+ git__utf8_to_16(link_w, GIT_WIN_PATH, link); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading@@ -169,8 +194,6 @@ FILE_FLAG_BACKUP_SEMANTICS, // normal file NULL); // no attr. template - git__free(link_w);- if (hFile == INVALID_HANDLE_VALUE) { giterr_set(GITERR_OS, "Cannot open '%s' for reading", link); return -1;@@ -217,46 +240,43 @@ return dwRet; } +int p_symlink(const char *old, const char *new)+{+ /* Real symlinks on NTFS require admin privileges. Until this changes,+ * libgit2 just creates a text file with the link target in the contents.+ */+ return git_futils_fake_symlink(old, new);+}+ int p_open(const char *path, int flags, ...) {- int fd;- wchar_t* buf;+ wchar_t buf[GIT_WIN_PATH]; mode_t mode = 0; - buf = gitwin_to_utf16(path);- if (!buf)- return -1;+ git__utf8_to_16(buf, GIT_WIN_PATH, path); - if (flags & O_CREAT)- {+ if (flags & O_CREAT) { va_list arg_list; va_start(arg_list, flags);- mode = va_arg(arg_list, mode_t);+ mode = (mode_t)va_arg(arg_list, int); va_end(arg_list); } - fd = _wopen(buf, flags | _O_BINARY, mode);-- git__free(buf);- return fd;+ return _wopen(buf, flags | _O_BINARY, mode); } int p_creat(const char *path, mode_t mode) {- int fd;- wchar_t* buf = gitwin_to_utf16(path);- if (!buf)- return -1;- fd = _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode);- git__free(buf);- return fd;+ wchar_t buf[GIT_WIN_PATH];+ git__utf8_to_16(buf, GIT_WIN_PATH, path);+ return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } int p_getcwd(char *buffer_out, size_t size) { int ret;- wchar_t* buf;+ wchar_t *buf; if ((size_t)((int)size) != size) return -1;@@ -275,104 +295,78 @@ int p_stat(const char* path, struct stat* buf) {- return do_lstat(path, buf);+ return do_lstat(path, buf, 0); } int p_chdir(const char* path) {- wchar_t* buf = gitwin_to_utf16(path);- int ret;- if (!buf)- return -1;- ret = _wchdir(buf);- git__free(buf);- return ret;+ wchar_t buf[GIT_WIN_PATH];+ git__utf8_to_16(buf, GIT_WIN_PATH, path);+ return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) {- wchar_t* buf = gitwin_to_utf16(path);- int ret;- if (!buf)- return -1;- ret = _wchmod(buf, mode);- git__free(buf);- return ret;+ wchar_t buf[GIT_WIN_PATH];+ git__utf8_to_16(buf, GIT_WIN_PATH, path);+ return _wchmod(buf, mode); } int p_rmdir(const char* path) {- wchar_t* buf = gitwin_to_utf16(path);- int ret;- if (!buf)- return -1;- ret = _wrmdir(buf);- git__free(buf);- return ret;+ wchar_t buf[GIT_WIN_PATH];+ git__utf8_to_16(buf, GIT_WIN_PATH, path);+ return _wrmdir(buf); } int p_hide_directory__w32(const char *path) {- int res;- wchar_t* buf = gitwin_to_utf16(path);- if (!buf)- return -1;-- res = SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN);- git__free(buf);-- return (res != 0) ? 0 : -1; /* MSDN states a "non zero" value indicates a success */+ wchar_t buf[GIT_WIN_PATH];+ git__utf8_to_16(buf, GIT_WIN_PATH, path);+ return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } char *p_realpath(const char *orig_path, char *buffer) {- int ret, buffer_sz = 0;- wchar_t* orig_path_w = gitwin_to_utf16(orig_path);- wchar_t* buffer_w = (wchar_t*)git__malloc(GIT_PATH_MAX * sizeof(wchar_t));+ int ret;+ wchar_t orig_path_w[GIT_WIN_PATH];+ wchar_t buffer_w[GIT_WIN_PATH]; - if (!orig_path_w || !buffer_w)- return NULL;+ git__utf8_to_16(orig_path_w, GIT_WIN_PATH, orig_path); - ret = GetFullPathNameW(orig_path_w, GIT_PATH_MAX, buffer_w, NULL);- git__free(orig_path_w);+ /* Implicitly use GetCurrentDirectory which can be a threading issue */+ ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH, buffer_w, NULL); /* According to MSDN, a return value equals to zero means a failure. */- if (ret == 0 || ret > GIT_PATH_MAX) {+ if (ret == 0 || ret > GIT_WIN_PATH) buffer = NULL;- goto done;++ else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {+ buffer = NULL;+ errno = ENOENT; } - if (buffer == NULL) {- buffer_sz = WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL);+ else if (buffer == NULL) {+ int buffer_sz = WideCharToMultiByte(+ CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL); if (!buffer_sz || !(buffer = (char *)git__malloc(buffer_sz)) ||- !WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL))+ !WideCharToMultiByte(+ CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL)) { git__free(buffer); buffer = NULL;- goto done; }- } else {- if (!WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) {- buffer = NULL;- goto done;- } } - if (!git_path_exists(buffer))- {- if (buffer_sz > 0)- git__free(buffer);-+ else if (!WideCharToMultiByte(+ CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) buffer = NULL;- errno = ENOENT;- } -done:- git__free(buffer_w); if (buffer) git_path_mkposix(buffer);+ return buffer; } @@ -381,7 +375,8 @@ #ifdef _MSC_VER int len; - if (count == 0 || (len = _vsnprintf(buffer, count, format, argptr)) < 0)+ if (count == 0 ||+ (len = _vsnprintf_s(buffer, count, _TRUNCATE, format, argptr)) < 0) return _vscprintf(format, argptr); return len;@@ -414,7 +409,7 @@ return -1; #endif - return p_creat(tmp_path, 0744);+ return p_creat(tmp_path, 0744); //-V536 } int p_setenv(const char* name, const char* value, int overwrite)@@ -427,32 +422,19 @@ int p_access(const char* path, mode_t mode) {- wchar_t *buf = gitwin_to_utf16(path);- int ret;- if (!buf)- return -1;-- ret = _waccess(buf, mode);- git__free(buf);-- return ret;+ wchar_t buf[GIT_WIN_PATH];+ git__utf8_to_16(buf, GIT_WIN_PATH, path);+ return _waccess(buf, mode); } int p_rename(const char *from, const char *to) {- wchar_t *wfrom = gitwin_to_utf16(from);- wchar_t *wto = gitwin_to_utf16(to);- int ret;-- if (!wfrom || !wto)- return -1;-- ret = MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1;-- git__free(wfrom);- git__free(wto);+ wchar_t wfrom[GIT_WIN_PATH];+ wchar_t wto[GIT_WIN_PATH]; - return ret;+ git__utf8_to_16(wfrom, GIT_WIN_PATH, from);+ git__utf8_to_16(wto, GIT_WIN_PATH, to);+ return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; } int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags)@@ -469,4 +451,125 @@ return -1; /* giterr_set will be done by caller */ return send(socket, buffer, (int)length, flags);+}++/**+ * Borrowed from http://old.nabble.com/Porting-localtime_r-and-gmtime_r-td15282276.html+ * On Win32, `gmtime_r` doesn't exist but `gmtime` is threadsafe, so we can use that+ */+struct tm * +p_localtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = localtime (timer); ++ if (local_result == NULL || result == NULL) + return NULL; ++ memcpy (result, local_result, sizeof (struct tm)); + return result; +} +struct tm * +p_gmtime_r (const time_t *timer, struct tm *result) +{ + struct tm *local_result; + local_result = gmtime (timer); ++ if (local_result == NULL || result == NULL) + return NULL; ++ memcpy (result, local_result, sizeof (struct tm)); + return result; +}++#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64+#else+#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL+#endif+ +#ifndef _TIMEZONE_DEFINED+#define _TIMEZONE_DEFINED+struct timezone +{+ int tz_minuteswest; /* minutes W of Greenwich */+ int tz_dsttime; /* type of dst correction */+};+#endif+ +int p_gettimeofday(struct timeval *tv, struct timezone *tz)+{+ FILETIME ft;+ unsigned __int64 tmpres = 0;+ static int tzflag;+ + if (NULL != tv)+ {+ GetSystemTimeAsFileTime(&ft);+ + tmpres |= ft.dwHighDateTime;+ tmpres <<= 32;+ tmpres |= ft.dwLowDateTime;+ + /*converting file time to unix epoch*/+ tmpres /= 10; /*convert into microseconds*/+ tmpres -= DELTA_EPOCH_IN_MICROSECS; + tv->tv_sec = (long)(tmpres / 1000000UL);+ tv->tv_usec = (long)(tmpres % 1000000UL);+ }+ + if (NULL != tz)+ {+ if (!tzflag)+ {+ _tzset();+ tzflag++;+ }+ tz->tz_minuteswest = _timezone / 60;+ tz->tz_dsttime = _daylight;+ }+ + return 0;+}++int p_inet_pton(int af, const char* src, void* dst)+{+ union {+ struct sockaddr_in6 sin6;+ struct sockaddr_in sin;+ } sa;+ int srcsize;++ switch(af)+ {+ case AF_INET:+ sa.sin.sin_family = AF_INET;+ srcsize = (int)sizeof(sa.sin);+ break;+ case AF_INET6:+ sa.sin6.sin6_family = AF_INET6;+ srcsize = (int)sizeof(sa.sin6);+ break;+ default:+ errno = WSAEPFNOSUPPORT;+ return -1;+ }++ if (WSAStringToAddress((LPSTR)src, af, NULL, (struct sockaddr *) &sa, &srcsize) != 0)+ {+ errno = WSAGetLastError();+ return -1;+ }++ switch(af)+ {+ case AF_INET:+ memcpy(dst, &sa.sin.sin_addr, sizeof(sa.sin.sin_addr));+ break;+ case AF_INET6:+ memcpy(dst, &sa.sin6.sin6_addr, sizeof(sa.sin6.sin6_addr));+ break;+ }++ return 1; }
+ libgit2/src/win32/precompiled.c view
@@ -0,0 +1,1 @@+#include "precompiled.h"
+ libgit2/src/win32/precompiled.h view
@@ -0,0 +1,19 @@+#include "git2.h"++#include <assert.h>+#include <errno.h>+#include <limits.h>+#include <stdlib.h>+#include <stdio.h>+#include <string.h>++#include <sys/types.h>+#include <sys/stat.h>++#include <regex.h>++#include <io.h>+#include <direct.h>+#ifdef GIT_THREADS+ #include "win32/pthread.h"+#endif
libgit2/src/win32/pthread.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -53,6 +53,74 @@ LeaveCriticalSection(mutex); return 0; }++int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)+{+ /* We don't support non-default attributes. */+ if (attr)+ return EINVAL;++ /* This is an auto-reset event. */+ *cond = CreateEventW(NULL, FALSE, FALSE, NULL);+ assert(*cond);++ /* If we can't create the event, claim that the reason was out-of-memory.+ * The actual reason can be fetched with GetLastError(). */+ return *cond ? 0 : ENOMEM;+}++int pthread_cond_destroy(pthread_cond_t *cond)+{+ BOOL closed;++ if (!cond)+ return EINVAL;++ closed = CloseHandle(*cond);+ assert(closed);+ GIT_UNUSED(closed);++ *cond = NULL;+ return 0;+}++int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)+{+ int error;+ DWORD wait_result;++ if (!cond || !mutex)+ return EINVAL;++ /* The caller must be holding the mutex. */+ error = pthread_mutex_unlock(mutex);++ if (error)+ return error;++ wait_result = WaitForSingleObject(*cond, INFINITE);+ assert(WAIT_OBJECT_0 == wait_result);+ GIT_UNUSED(wait_result);++ return pthread_mutex_lock(mutex);+}++int pthread_cond_signal(pthread_cond_t *cond)+{+ BOOL signaled;++ if (!cond)+ return EINVAL;++ signaled = SetEvent(*cond);+ assert(signaled);+ GIT_UNUSED(signaled);++ return 0;+}++/* pthread_cond_broadcast is not implemented because doing so with just Win32 events+ * is quite complicated, and no caller in libgit2 uses it yet. */ int pthread_num_processors_np(void) {
libgit2/src/win32/pthread.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -21,6 +21,7 @@ typedef int pthread_attr_t; typedef CRITICAL_SECTION pthread_mutex_t; typedef HANDLE pthread_t;+typedef HANDLE pthread_cond_t; #define PTHREAD_MUTEX_INITIALIZER {(void*)-1}; @@ -34,6 +35,12 @@ int pthread_mutex_destroy(pthread_mutex_t *); int pthread_mutex_lock(pthread_mutex_t *); int pthread_mutex_unlock(pthread_mutex_t *);++int pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *);+int pthread_cond_destroy(pthread_cond_t *);+int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *);+int pthread_cond_signal(pthread_cond_t *);+/* pthread_cond_broadcast is not supported on Win32 yet. */ int pthread_num_processors_np(void);
libgit2/src/win32/utf-conv.c view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -7,89 +7,75 @@ #include "common.h" #include "utf-conv.h"-#include "git2/windows.h" -/*- * Default codepage value- */-static int _active_codepage = CP_UTF8;--void gitwin_set_codepage(unsigned int codepage)-{- _active_codepage = codepage;-}--unsigned int gitwin_get_codepage(void)-{- return _active_codepage;-}--void gitwin_set_utf8(void)-{- _active_codepage = CP_UTF8;-}+#define U16_LEAD(c) (wchar_t)(((c)>>10)+0xd7c0)+#define U16_TRAIL(c) (wchar_t)(((c)&0x3ff)|0xdc00) -wchar_t* gitwin_to_utf16(const char* str)+#if 0+void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) {- wchar_t* ret;- size_t cb;+ wchar_t *pDest = dest;+ uint32_t ch;+ const uint8_t* pSrc = (uint8_t*) src; - if (!str)- return NULL;+ assert(dest && src && length); - cb = strlen(str) * sizeof(wchar_t);- if (cb == 0)- return (wchar_t *)git__calloc(1, sizeof(wchar_t));+ length--; - /* Add space for null terminator */- cb += sizeof(wchar_t);+ while(*pSrc && length > 0) {+ ch = *pSrc++;+ length--; - ret = (wchar_t *)git__malloc(cb);- if (!ret)- return NULL;+ if(ch < 0xc0) {+ /*+ * ASCII, or a trail byte in lead position which is treated like+ * a single-byte sequence for better character boundary+ * resynchronization after illegal sequences.+ */+ *pDest++ = (wchar_t)ch;+ continue;+ } else if(ch < 0xe0) { /* U+0080..U+07FF */+ if (pSrc[0]) {+ /* 0x3080 = (0xc0 << 6) + 0x80 */+ *pDest++ = (wchar_t)((ch << 6) + *pSrc++ - 0x3080);+ continue;+ }+ } else if(ch < 0xf0) { /* U+0800..U+FFFF */+ if (pSrc[0] && pSrc[1]) {+ /* no need for (ch & 0xf) because the upper bits are truncated after <<12 in the cast to (UChar) */+ /* 0x2080 = (0x80 << 6) + 0x80 */+ ch = (ch << 12) + (*pSrc++ << 6);+ *pDest++ = (wchar_t)(ch + *pSrc++ - 0x2080);+ continue;+ }+ } else /* f0..f4 */ { /* U+10000..U+10FFFF */+ if (length >= 1 && pSrc[0] && pSrc[1] && pSrc[2]) {+ /* 0x3c82080 = (0xf0 << 18) + (0x80 << 12) + (0x80 << 6) + 0x80 */+ ch = (ch << 18) + (*pSrc++ << 12);+ ch += *pSrc++ << 6;+ ch += *pSrc++ - 0x3c82080;+ *(pDest++) = U16_LEAD(ch);+ *(pDest++) = U16_TRAIL(ch);+ length--; /* two bytes for this character */+ continue;+ }+ } - if (MultiByteToWideChar(_active_codepage, 0, str, -1, ret, (int)cb) == 0) {- giterr_set(GITERR_OS, "Could not convert string to UTF-16");- git__free(ret);- ret = NULL;+ /* truncated character at the end */+ *pDest++ = 0xfffd;+ break; } - return ret;+ *pDest++ = 0x0; }+#endif -int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len)+int git__utf8_to_16(wchar_t *dest, size_t length, const char *src) {- int result = MultiByteToWideChar(_active_codepage, 0, str, -1, buffer, (int)len);- if (result == 0)- giterr_set(GITERR_OS, "Could not convert string to UTF-16");- return result;+ return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length); } -char* gitwin_from_utf16(const wchar_t* str)+int git__utf16_to_8(char *out, const wchar_t *input) {- char* ret;- size_t cb;-- if (!str)- return NULL;-- cb = wcslen(str) * sizeof(char);- if (cb == 0)- return (char *)git__calloc(1, sizeof(char));-- /* Add space for null terminator */- cb += sizeof(char);-- ret = (char*)git__malloc(cb);- if (!ret)- return NULL;-- if (WideCharToMultiByte(_active_codepage, 0, str, -1, ret, (int)cb, NULL, NULL) == 0) {- giterr_set(GITERR_OS, "Could not convert string to UTF-8");- git__free(ret);- ret = NULL;- }-- return ret;-+ return WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL); }
libgit2/src/win32/utf-conv.h view
@@ -1,5 +1,5 @@ /*- * Copyright (C) 2009-2012 the libgit2 contributors+ * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file.@@ -10,9 +10,10 @@ #ifndef INCLUDE_git_utfconv_h__ #define INCLUDE_git_utfconv_h__ -wchar_t* gitwin_to_utf16(const char* str);-int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len);-char* gitwin_from_utf16(const wchar_t* str);+#define GIT_WIN_PATH (260 + 1)++int git__utf8_to_16(wchar_t *dest, size_t length, const char *src);+int git__utf16_to_8(char *dest, const wchar_t *src); #endif
+ libgit2/src/win32/version.h view
@@ -0,0 +1,20 @@+/*+ * Copyright (C) the libgit2 contributors. All rights reserved.+ *+ * This file is part of libgit2, distributed under the GNU GPL v2 with+ * a Linking Exception. For full terms see the included COPYING file.+ */+#ifndef INCLUDE_win32_version_h__+#define INCLUDE_win32_version_h__++#include <windows.h>++GIT_INLINE(int) git_has_win32_version(int major, int minor)+{+ WORD wVersion = LOWORD(GetVersion());++ return LOBYTE(wVersion) > major ||+ (LOBYTE(wVersion) == major && HIBYTE(wVersion) >= minor);+}++#endif
test/Main.hs view
@@ -15,10 +15,12 @@ _ <- system "git init smoke.git" putStrLn "Accessing directly..."+ c'git_threads_init alloca $ \ptr -> do withCString "smoke.git/.git" $ \str -> do r <- c'git_repository_open ptr str when (r < 0) $ exitWith (ExitFailure 1) peek ptr >>= c'git_repository_free+ c'git_threads_shutdown -- Main.hs ends here