packages feed

hlibgit2 0.17.0.8 → 0.18.0.1

raw patch · 297 files changed

+47858/−16221 lines, 297 files

Files

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(&regex, regexp, REG_EXTENDED)) < 0) {+			giterr_set_regex(&regex, result);+			regfree(&regex);+			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(&regex, 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(&regex);++	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(&regex, regex_str, REG_EXTENDED); 		if (result < 0) { 			giterr_set_regex(&regex, result);+			regfree(&regex); 			return -1; 		}  		/* and throw the callback only on the variables that 		 * match the regex */ 		do {-			if (regexec(&regex, var->value, 0, NULL, 0) == 0) {+			if (regexec(&regex, 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, &timestamp, &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   = &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(&note->oid, &oid);-	note->message = git__strdup(git_blob_rawcontent(blob));+	git_oid_cpy(&note->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(&note, &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(&notes_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, &notes_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(&notes_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, &notes_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(&notes_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, &notes_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 &note->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(&note_data.annotated_object_oid, buf.ptr) < 0)-		return -1;--	git_oid_cpy(&note_data.blob_oid, note_oid);--	error = note_cb(&note_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(&notes_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(&note_id, &annotated_id, iter))) {+        if (note_cb(&note_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, &notes_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*)&current[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(&regex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0)+		return -1;++	error = regexec(&regex, spec, 0, NULL, 0);+	regfree(&regex);++	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(&timestamp, 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, &param) < 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, &param); }  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, &param) < 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