summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Kelley <phkelley@hotmail.com>2012-11-28 11:42:37 -0500
committerPhilip Kelley <phkelley@hotmail.com>2012-11-28 11:42:37 -0500
commit613d5eb9391d6cc33c91f4dc9cdb5ede1885dc72 (patch)
treebd94a2a5ea154a3849114575368b7531f0d45024
parent693021262ba0eeac2923bbce1b2262717019c807 (diff)
downloadlibgit2-613d5eb9391d6cc33c91f4dc9cdb5ede1885dc72.tar.gz
Push! By schu, phkelley, and congyiwu, et al
-rw-r--r--include/git2.h1
-rw-r--r--include/git2/common.h5
-rw-r--r--include/git2/errors.h1
-rw-r--r--include/git2/object.h11
-rw-r--r--include/git2/push.h80
-rw-r--r--include/git2/transport.h40
-rw-r--r--include/git2/types.h1
-rw-r--r--src/object.c16
-rw-r--r--src/pack-objects.c16
-rw-r--r--src/pack-objects.h1
-rw-r--r--src/push.c422
-rw-r--r--src/push.h41
-rw-r--r--src/reflog.h2
-rw-r--r--src/remote.c79
-rw-r--r--src/remote.h1
-rw-r--r--src/transport.c7
-rw-r--r--src/transports/git.c89
-rw-r--r--src/transports/http.c383
-rw-r--r--src/transports/local.c11
-rw-r--r--src/transports/smart.c209
-rw-r--r--src/transports/smart.h29
-rw-r--r--src/transports/smart_pkt.c114
-rw-r--r--src/transports/smart_protocol.c244
-rw-r--r--src/transports/winhttp.c583
-rw-r--r--tests-clar/network/fetch.c2
-rw-r--r--tests-clar/network/push.c518
-rw-r--r--tests-clar/network/push_util.c126
-rw-r--r--tests-clar/network/push_util.h68
-rw-r--r--tests-clar/object/lookup.c12
-rw-r--r--tests-clar/resources/push.sh55
-rw-r--r--tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG1
-rw-r--r--tests-clar/resources/push_src/.gitted/HEAD1
-rw-r--r--tests-clar/resources/push_src/.gitted/ORIG_HEAD1
-rw-r--r--tests-clar/resources/push_src/.gitted/config10
-rw-r--r--tests-clar/resources/push_src/.gitted/description1
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/applypatch-msg.sample15
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/commit-msg.sample24
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/post-commit.sample8
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/post-receive.sample15
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/post-update.sample8
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/pre-applypatch.sample14
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/pre-commit.sample50
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/pre-rebase.sample169
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/prepare-commit-msg.sample36
-rw-r--r--tests-clar/resources/push_src/.gitted/hooks/update.sample128
-rw-r--r--tests-clar/resources/push_src/.gitted/indexbin0 -> 470 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/info/exclude6
-rw-r--r--tests-clar/resources/push_src/.gitted/logs/HEAD10
-rw-r--r--tests-clar/resources/push_src/.gitted/logs/refs/heads/b11
-rw-r--r--tests-clar/resources/push_src/.gitted/logs/refs/heads/b21
-rw-r--r--tests-clar/resources/push_src/.gitted/logs/refs/heads/b32
-rw-r--r--tests-clar/resources/push_src/.gitted/logs/refs/heads/b42
-rw-r--r--tests-clar/resources/push_src/.gitted/logs/refs/heads/b52
-rw-r--r--tests-clar/resources/push_src/.gitted/logs/refs/heads/master3
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/HEAD1
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/config15
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/description1
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/applypatch-msg.sample15
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/commit-msg.sample24
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-commit.sample8
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-receive.sample15
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-update.sample8
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-applypatch.sample14
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-commit.sample50
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-rebase.sample169
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/prepare-commit-msg.sample36
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/hooks/update.sample128
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/indexbin0 -> 256 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude6
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD1
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master1
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD1
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125bin0 -> 54 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08bin0 -> 19 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7bin0 -> 51 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccdbin0 -> 119 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fdbin0 -> 122 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689bin0 -> 51 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9bbin0 -> 21 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10bin0 -> 168 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924dbin0 -> 21 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7bin0 -> 44 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54bin0 -> 50 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022ccbin0 -> 23 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057bin0 -> 18 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd20452
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796eabin0 -> 44 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91bin0 -> 152 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf36442
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60abin0 -> 119 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af1
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980bin0 -> 145 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344dbin0 -> 82 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479bin0 -> 126 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe2
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe181621
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4bin0 -> 50 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d22
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a3
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f2
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe45477503
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bdbin0 -> 28 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6bin0 -> 26 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12bin0 -> 148 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d12
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593bin0 -> 80 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f6443
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd3
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659bin0 -> 149 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487fbin0 -> 21 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759bin0 -> 79 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0bin0 -> 21 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3bin0 -> 103 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1bin0 -> 82 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92bin0 -> 24 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765bin0 -> 82 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09bin0 -> 152 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idxbin0 -> 46656 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.packbin0 -> 386089 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idxbin0 -> 1240 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.packbin0 -> 491 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idxbin0 -> 1240 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.packbin0 -> 498 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/packed-refs24
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/refs/heads/master1
-rw-r--r--tests-clar/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD1
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca1
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d4
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840bin0 -> 109 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60bin0 -> 20 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915bin0 -> 128 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472bin0 -> 17 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4dbin0 -> 176 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85bin0 -> 17 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce2
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247bin0 -> 166 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fdbin0 -> 148 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279bin0 -> 22 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4cbin0 -> 80 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e42
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591bin0 -> 80 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928bin0 -> 65 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1abin0 -> 50 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a22
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925bin0 -> 109 bytes
-rw-r--r--tests-clar/resources/push_src/.gitted/objects/pack/dummy0
-rw-r--r--tests-clar/resources/push_src/.gitted/refs/heads/b11
-rw-r--r--tests-clar/resources/push_src/.gitted/refs/heads/b21
-rw-r--r--tests-clar/resources/push_src/.gitted/refs/heads/b31
-rw-r--r--tests-clar/resources/push_src/.gitted/refs/heads/b41
-rw-r--r--tests-clar/resources/push_src/.gitted/refs/heads/b51
-rw-r--r--tests-clar/resources/push_src/.gitted/refs/heads/b61
-rw-r--r--tests-clar/resources/push_src/a.txt2
-rw-r--r--tests-clar/resources/push_src/fold/b.txt1
-rw-r--r--tests-clar/resources/push_src/foldb.txt1
-rw-r--r--tests-clar/resources/push_src/gitmodules3
-rw-r--r--tests-clar/resources/push_src/submodule/.gitted1
-rw-r--r--tests-clar/resources/push_src/submodule/README1
-rw-r--r--tests-clar/resources/push_src/submodule/branch_file.txt2
-rw-r--r--tests-clar/resources/push_src/submodule/new.txt1
161 files changed, 3905 insertions, 344 deletions
diff --git a/include/git2.h b/include/git2.h
index 501128c43..fe8b02103 100644
--- a/include/git2.h
+++ b/include/git2.h
@@ -40,6 +40,7 @@
#include "git2/remote.h"
#include "git2/clone.h"
#include "git2/checkout.h"
+#include "git2/push.h"
#include "git2/attr.h"
#include "git2/ignore.h"
diff --git a/include/git2/common.h b/include/git2/common.h
index dd6909f90..ad23d2d34 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -86,6 +86,11 @@ GIT_BEGIN_DECL
#define GIT_PATH_MAX 4096
/**
+ * The string representation of the null object ID.
+ */
+#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000"
+
+/**
* Return the version of the libgit2 library
* being currently used.
*
diff --git a/include/git2/errors.h b/include/git2/errors.h
index 45e04578d..9dd42f0c4 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -29,6 +29,7 @@ enum {
GIT_EBAREREPO = -8,
GIT_EORPHANEDHEAD = -9,
GIT_EUNMERGED = -10,
+ GIT_ENONFASTFORWARD = -11,
GIT_PASSTHROUGH = -30,
GIT_ITEROVER = -31,
diff --git a/include/git2/object.h b/include/git2/object.h
index fcc56cb27..66d692161 100644
--- a/include/git2/object.h
+++ b/include/git2/object.h
@@ -95,6 +95,17 @@ GIT_EXTERN(const git_oid *) git_object_id(const git_object *obj);
GIT_EXTERN(git_otype) git_object_type(const git_object *obj);
/**
+ * Get the object type of an object id
+ *
+ * @param obj the repository object
+ * @return the object's type
+ */
+GIT_EXTERN(int) git_object_oid2type(
+ git_otype *type,
+ git_repository *repo,
+ const git_oid *oid);
+
+/**
* Get the repository that owns this object
*
* Freeing or calling `git_repository_close` on the
diff --git a/include/git2/push.h b/include/git2/push.h
new file mode 100644
index 000000000..900a1833e
--- /dev/null
+++ b/include/git2/push.h
@@ -0,0 +1,80 @@
+/*
+ * 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_push_h__
+#define INCLUDE_git_push_h__
+
+#include "common.h"
+
+/**
+ * @file git2/push.h
+ * @brief Git push management functions
+ * @defgroup git_push push management functions
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * Actually push all given refspecs
+ *
+ * @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
+ *
+ * @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
diff --git a/include/git2/transport.h b/include/git2/transport.h
index 5a27d7f97..61726922f 100644
--- a/include/git2/transport.h
+++ b/include/git2/transport.h
@@ -9,6 +9,7 @@
#include "indexer.h"
#include "net.h"
+#include "types.h"
/**
* @file git2/transport.h
@@ -102,8 +103,8 @@ typedef struct git_transport {
git_headlist_cb list_cb,
void *payload);
- /* Reserved until push is implemented. */
- int (*push)(struct git_transport *transport);
+ /* 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
@@ -123,7 +124,7 @@ typedef struct git_transport {
void *progress_payload);
/* Checks to see if the transport is connected */
- int (*is_connected)(struct git_transport *transport, int *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);
@@ -145,10 +146,11 @@ typedef struct git_transport {
* 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, const char *url);
+GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const char *url);
/**
* Function which checks to see if a transport could be created for the
@@ -161,7 +163,7 @@ GIT_EXTERN(int) git_transport_new(git_transport **out, const char *url);
GIT_EXTERN(int) git_transport_valid_url(const char *url);
/* Signature of a function which creates a transport */
-typedef int (*git_transport_cb)(git_transport **out, void *payload);
+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. */
@@ -169,34 +171,40 @@ typedef int (*git_transport_cb)(git_transport **out, void *payload);
/**
* Create an instance of the dummy transport.
*
- * @param transport The newly created transport (out)
+ * @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 **transport,
+ git_transport **out,
+ git_remote *owner,
/* NULL */ void *payload);
/**
* Create an instance of the local transport.
*
- * @param transport The newly created transport (out)
+ * @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 **transport,
+ git_transport **out,
+ git_remote *owner,
/* NULL */ void *payload);
/**
* Create an instance of the smart transport.
*
- * @param transport The newly created transport (out)
+ * @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 **transport,
+ git_transport **out,
+ git_remote *owner,
/* (git_smart_subtransport_definition *) */ void *payload);
/*
@@ -221,6 +229,8 @@ GIT_EXTERN(int) git_transport_smart(
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;
@@ -255,6 +265,14 @@ typedef struct git_smart_subtransport {
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;
diff --git a/include/git2/types.h b/include/git2/types.h
index 11bcf270f..06fcf3613 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -187,6 +187,7 @@ typedef enum {
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;
diff --git a/src/object.c b/src/object.c
index 392fd80a8..0666c4466 100644
--- a/src/object.c
+++ b/src/object.c
@@ -373,3 +373,19 @@ int git_object_peel(
git_object_free(deref);
return -1;
}
+
+int git_object_oid2type(
+ git_otype *type,
+ git_repository *repo,
+ const git_oid *oid)
+{
+ git_object *obj;
+
+ if (git_object_lookup(&obj, repo, oid, GIT_OBJ_ANY) < 0)
+ return -1;
+
+ *type = git_object_type(obj);
+
+ git_object_free(obj);
+ return 0;
+}
diff --git a/src/pack-objects.c b/src/pack-objects.c
index 008d8f288..44ad3fd98 100644
--- a/src/pack-objects.c
+++ b/src/pack-objects.c
@@ -604,12 +604,6 @@ on_error:
return -1;
}
-static int send_pack_file(void *buf, size_t size, void *data)
-{
- gitno_socket *s = (gitno_socket *)data;
- return gitno_send(s, buf, size, 0);
-}
-
static int write_pack_buf(void *buf, size_t size, void *data)
{
git_buf *b = (git_buf *)data;
@@ -1233,12 +1227,6 @@ static int prepare_pack(git_packbuilder *pb)
#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }
-int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s)
-{
- PREPARE_PACK;
- return write_pack(pb, &send_pack_file, s);
-}
-
int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload)
{
PREPARE_PACK;
@@ -1264,6 +1252,10 @@ static int cb_tree_walk(const char *root, const git_tree_entry *entry, void *pay
git_packbuilder *pb = payload;
git_buf buf = GIT_BUF_INIT;
+ /* A commit inside a tree represents a submodule commit and should be skipped. */
+ if(git_tree_entry_type(entry) == GIT_OBJ_COMMIT)
+ return 0;
+
git_buf_puts(&buf, root);
git_buf_puts(&buf, git_tree_entry_name(entry));
diff --git a/src/pack-objects.h b/src/pack-objects.h
index e34cc2754..70ee72ce9 100644
--- a/src/pack-objects.h
+++ b/src/pack-objects.h
@@ -82,7 +82,6 @@ struct git_packbuilder {
bool done;
};
-int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s);
int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb);
#endif /* INCLUDE_pack_objects_h__ */
diff --git a/src/push.c b/src/push.c
new file mode 100644
index 000000000..1d63d574e
--- /dev/null
+++ b/src/push.c
@@ -0,0 +1,422 @@
+/*
+ * 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.h"
+
+#include "common.h"
+#include "pack.h"
+#include "pack-objects.h"
+#include "remote.h"
+#include "vector.h"
+#include "push.h"
+
+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;
+
+ if (git_vector_init(&p->specs, 0, NULL) < 0) {
+ git__free(p);
+ return -1;
+ }
+
+ if (git_vector_init(&p->status, 0, NULL) < 0) {
+ git_vector_free(&p->specs);
+ git__free(p);
+ return -1;
+ }
+
+ *out = p;
+ 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 void free_status(push_status *status)
+{
+ if (status == NULL)
+ return;
+
+ if (status->msg)
+ git__free(status->msg);
+
+ git__free(status->ref);
+ git__free(status);
+}
+
+static int check_ref(char *ref)
+{
+ if (strcmp(ref, "HEAD") &&
+ git__prefixcmp(ref, "refs/heads/") &&
+ git__prefixcmp(ref, "refs/tags/")) {
+ giterr_set(GITERR_INVALID, "No valid reference '%s'", ref);
+ return -1;
+ }
+ return 0;
+}
+
+static int parse_refspec(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++;
+ }
+
+#define check(ref) \
+ if (!ref || check_ref(ref) < 0) goto on_error
+
+ delim = strchr(str, ':');
+ if (delim == NULL) {
+ s->lref = git__strdup(str);
+ check(s->lref);
+ s->rref = NULL;
+ } else {
+ if (delim - str) {
+ s->lref = git__strndup(str, delim - str);
+ check(s->lref);
+ } else
+ s->lref = NULL;
+
+ if (strlen(delim + 1)) {
+ s->rref = git__strdup(delim + 1);
+ check(s->rref);
+ } else
+ s->rref = NULL;
+ }
+
+ if (!s->lref && !s->rref)
+ goto on_error;
+
+#undef check
+
+ *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(&spec, refspec) < 0 ||
+ git_vector_insert(&push->specs, spec) < 0)
+ return -1;
+
+ return 0;
+}
+
+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) {
+ 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_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 queue_objects(git_push *push)
+{
+ git_vector commits;
+ git_oid *o;
+ unsigned int i;
+ int error;
+
+ if (git_vector_init(&commits, 0, NULL) < 0)
+ return -1;
+
+ if ((error = revwalk(&commits, push)) < 0)
+ goto on_error;
+
+ if (!commits.length) {
+ git_vector_free(&commits);
+ return 0; /* nothing to do */
+ }
+
+ git_vector_foreach(&commits, i, o) {
+ if ((error = git_packbuilder_insert(push->pb, o, NULL)) < 0)
+ goto on_error;
+ }
+
+ git_vector_foreach(&commits, i, o) {
+ git_object *obj;
+
+ if ((error = git_object_lookup(&obj, push->repo, o, GIT_OBJ_ANY)) < 0)
+ goto on_error;
+
+ switch (git_object_type(obj)) {
+ case GIT_OBJ_TAG: /* TODO: expect tags */
+ case GIT_OBJ_COMMIT:
+ if ((error = git_packbuilder_insert_tree(push->pb,
+ git_commit_tree_id((git_commit *)obj))) < 0) {
+ git_object_free(obj);
+ goto on_error;
+ }
+ break;
+ case GIT_OBJ_TREE:
+ case GIT_OBJ_BLOB:
+ default:
+ git_object_free(obj);
+ giterr_set(GITERR_INVALID, "Given object type invalid");
+ error = -1;
+ goto on_error;
+ }
+ git_object_free(obj);
+ }
+ error = 0;
+
+on_error:
+ git_vector_foreach(&commits, i, o) {
+ git__free(o);
+ }
+ git_vector_free(&commits);
+ return error;
+}
+
+static int calculate_work(git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ unsigned int i, j;
+
+ git_vector_foreach(&push->specs, i, spec) {
+ if (spec->lref) {
+ 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) {
+ /*
+ * No remote reference given; if we find a remote
+ * reference with the same name we will update it,
+ * otherwise a new reference will be created.
+ */
+ git_vector_foreach(&push->remote->refs, j, head) {
+ if (!strcmp(spec->lref, head->name)) {
+ /*
+ * Update remote reference
+ */
+ git_oid_cpy(&spec->roid, &head->oid);
+
+ break;
+ }
+ }
+ } else {
+ /*
+ * Remote reference given; update the given
+ * reference or create it.
+ */
+ git_vector_foreach(&push->remote->refs, j, head) {
+ if (!strcmp(spec->rref, head->name)) {
+ /*
+ * Update remote reference
+ */
+ 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;
+
+ /*
+ * 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 ||
+ (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_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) {
+ free_status(status);
+ }
+ git_vector_free(&push->status);
+
+ git__free(push);
+}
diff --git a/src/push.h b/src/push.h
new file mode 100644
index 000000000..1a2fb0260
--- /dev/null
+++ b/src/push.h
@@ -0,0 +1,41 @@
+/*
+ * 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_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;
+};
+
+#endif
diff --git a/src/reflog.h b/src/reflog.h
index 3bbdf6e10..749cbc688 100644
--- a/src/reflog.h
+++ b/src/reflog.h
@@ -17,8 +17,6 @@
#define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17)
-#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000"
-
struct git_reflog_entry {
git_oid oid_old;
git_oid oid_cur;
diff --git a/src/remote.c b/src/remote.c
index bdec3c1f4..c84911aa1 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -488,13 +488,13 @@ int git_remote_connect(git_remote *remote, git_direction direction)
/* A transport could have been supplied in advance with
* git_remote_set_transport */
- if (!t && git_transport_new(&t, url) < 0)
+ 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;
@@ -507,6 +507,10 @@ int git_remote_connect(git_remote *remote, git_direction direction)
on_error:
t->free(t);
+
+ if (t == remote->transport)
+ remote->transport = NULL;
+
return -1;
}
@@ -514,7 +518,7 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{
assert(remote);
- if (!remote->transport) {
+ if (!git_remote_connected(remote)) {
giterr_set(GITERR_NET, "The remote is not connected");
return -1;
}
@@ -522,6 +526,63 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
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)
+ 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,
@@ -687,7 +748,7 @@ int git_remote_update_tips(git_remote *remote)
git_vector_init(&update_heads, 16, NULL) < 0)
return -1;
- if (remote->transport->ls(remote->transport, update_tips_callback, &refs) < 0)
+ 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. */
@@ -779,22 +840,20 @@ on_error:
int git_remote_connected(git_remote *remote)
{
- int connected;
-
assert(remote);
if (!remote->transport || !remote->transport->is_connected)
return 0;
/* Ask the transport if it's connected. */
- remote->transport->is_connected(remote->transport, &connected);
-
- return connected;
+ return remote->transport->is_connected(remote->transport);
}
void git_remote_stop(git_remote *remote)
{
- if (remote->transport->cancel)
+ assert(remote);
+
+ if (remote->transport && remote->transport->cancel)
remote->transport->cancel(remote->transport);
}
diff --git a/src/remote.h b/src/remote.h
index 448a9e9a9..06f712fbc 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -34,5 +34,6 @@ struct git_remote {
};
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
diff --git a/src/transport.c b/src/transport.c
index 8c242af6d..9c88d983e 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -76,15 +76,16 @@ static int transport_find_fn(const char *url, git_transport_cb *callback, void *
* Public API *
**************/
-int git_transport_dummy(git_transport **transport, void *param)
+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;
@@ -96,7 +97,7 @@ int git_transport_new(git_transport **out, const char *url)
return -1;
}
- error = fn(&transport, param);
+ error = fn(&transport, owner, param);
if (error < 0)
return error;
diff --git a/src/transports/git.c b/src/transports/git.c
index a895c1389..c931dd82b 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -13,6 +13,7 @@
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_smart_subtransport_stream parent;
@@ -173,7 +174,7 @@ static int git_stream_alloc(
return 0;
}
-static int git_git_uploadpack_ls(
+static int _git_uploadpack_ls(
git_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
@@ -211,7 +212,7 @@ on_error:
return -1;
}
-static int git_git_uploadpack(
+static int _git_uploadpack(
git_subtransport *t,
const char *url,
git_smart_subtransport_stream **stream)
@@ -227,29 +228,100 @@ static int git_git_uploadpack(
return -1;
}
+static int _git_receivepack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ char *host, *port;
+ git_stream *s;
+
+ *stream = NULL;
+
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
+
+ if (git_stream_alloc(t, url, cmd_receivepack, stream) < 0)
+ return -1;
+
+ s = (git_stream *)*stream;
+
+ if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
+ goto on_error;
+
+ if (gitno_connect(&s->socket, host, port, 0) < 0)
+ goto on_error;
+
+ t->current_stream = s;
+ git__free(host);
+ git__free(port);
+ return 0;
+
+on_error:
+ if (*stream)
+ git_stream_free(*stream);
+
+ git__free(host);
+ git__free(port);
+ return -1;
+}
+
+static int _git_receivepack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ GIT_UNUSED(url);
+
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
+ return -1;
+}
+
static int _git_action(
git_smart_subtransport_stream **stream,
- git_smart_subtransport *smart_transport,
+ git_smart_subtransport *subtransport,
const char *url,
git_smart_service_t action)
{
- git_subtransport *t = (git_subtransport *) smart_transport;
+ git_subtransport *t = (git_subtransport *) subtransport;
switch (action) {
case GIT_SERVICE_UPLOADPACK_LS:
- return git_git_uploadpack_ls(t, url, stream);
+ return _git_uploadpack_ls(t, url, stream);
case GIT_SERVICE_UPLOADPACK:
- return git_git_uploadpack(t, url, stream);
+ return _git_uploadpack(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK_LS:
+ return _git_receivepack_ls(t, url, stream);
+
+ case GIT_SERVICE_RECEIVEPACK:
+ return _git_receivepack(t, url, stream);
}
*stream = NULL;
return -1;
}
-static void _git_free(git_smart_subtransport *smart_transport)
+static int _git_close(git_smart_subtransport *subtransport)
+{
+ git_subtransport *t = (git_subtransport *) subtransport;
+
+ assert(!t->current_stream);
+
+ GIT_UNUSED(t);
+
+ return 0;
+}
+
+static void _git_free(git_smart_subtransport *subtransport)
{
- git_subtransport *t = (git_subtransport *) smart_transport;
+ git_subtransport *t = (git_subtransport *) subtransport;
assert(!t->current_stream);
@@ -268,6 +340,7 @@ int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owne
t->owner = owner;
t->parent.action = _git_action;
+ t->parent.close = _git_close;
t->parent.free = _git_free;
*out = (git_smart_subtransport *) t;
diff --git a/src/transports/http.c b/src/transports/http.c
index ba4d8746f..b8fc4fe79 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -17,6 +17,9 @@ 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";
@@ -26,6 +29,9 @@ static const char *basic_authtype = "Basic";
#define PARSE_ERROR_GENERIC -1
#define PARSE_ERROR_REPLAY -2
+#define CHUNK_SIZE 4096
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
enum last_cb {
NONE,
FIELD,
@@ -41,7 +47,11 @@ typedef struct {
const char *service;
const char *service_url;
const char *verb;
- unsigned sent_request : 1;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ unsigned sent_request : 1,
+ received_response : 1,
+ chunked : 1;
} http_stream;
typedef struct {
@@ -106,33 +116,33 @@ on_error:
static int gen_request(
git_buf *buf,
- const char *path,
- const char *host,
- git_cred *cred,
- http_authmechanism_t auth_mechanism,
- const char *op,
- const char *service,
- const char *service_url,
- ssize_t content_length)
+ http_stream *s,
+ size_t content_length)
{
- if (!path)
- path = "/";
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+
+ if (!t->path)
+ t->path = "/";
- git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url);
+ 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", 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_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 (cred && cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
- auth_mechanism == GIT_HTTP_AUTH_BASIC &&
- apply_basic_credential(buf, cred) < 0)
+ 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;
git_buf_puts(buf, "\r\n");
@@ -168,7 +178,7 @@ static int on_header_ready(http_subtransport *t)
git_buf *value = &t->parse_header_value;
char *dup;
- if (!t->content_type && !strcmp("Content-Type", git_buf_cstr(name))) {
+ if (!t->content_type && !strcasecmp("Content-Type", git_buf_cstr(name))) {
t->content_type = git__strdup(git_buf_cstr(value));
GITERR_CHECK_ALLOC(t->content_type);
}
@@ -355,6 +365,34 @@ static void clear_parser_state(http_subtransport *t)
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_stream_read(
git_smart_subtransport_stream *stream,
char *buffer,
@@ -363,8 +401,8 @@ static int http_stream_read(
{
http_stream *s = (http_stream *)stream;
http_subtransport *t = OWNING_SUBTRANSPORT(s);
- git_buf request = GIT_BUF_INIT;
parser_context ctx;
+ size_t bytes_parsed;
replay:
*bytes_read = 0;
@@ -372,11 +410,11 @@ replay:
assert(t->connected);
if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
clear_parser_state(t);
- if (gen_request(&request, t->path, t->host,
- t->cred, t->auth_mechanism, s->verb,
- s->service, s->service_url, 0) < 0) {
+ if (gen_request(&request, s, 0) < 0) {
giterr_set(GITERR_NET, "Failed to generate request");
return -1;
}
@@ -387,86 +425,182 @@ replay:
}
git_buf_free(&request);
+
s->sent_request = 1;
}
- t->parse_buffer.offset = 0;
+ if (!s->received_response) {
+ if (s->chunked) {
+ assert(s->verb == post_verb);
- if (t->parse_finished)
- return 0;
+ /* Flush, if necessary */
+ if (s->chunk_buffer_len > 0 &&
+ write_chunk(&t->socket, s->chunk_buffer, s->chunk_buffer_len) < 0)
+ return -1;
- if (gitno_recv(&t->parse_buffer) < 0)
- return -1;
+ s->chunk_buffer_len = 0;
- /* 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;
-
- 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;
- goto replay;
+ /* Write the final chunk. */
+ if (gitno_send(&t->socket, "0\r\n\r\n", 5, 0) < 0)
+ return -1;
+ }
+
+ s->received_response = 1;
}
- if (t->parse_error < 0)
- return -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;
+ 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(
+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);
- git_buf request = GIT_BUF_INIT;
assert(t->connected);
- /* Since we have to write the Content-Length header up front, we're
- * basically limited to a single call to write() per request. */
- assert(!s->sent_request);
-
+ /* Send the request, if necessary */
if (!s->sent_request) {
+ git_buf request = GIT_BUF_INIT;
+
clear_parser_state(t);
- if (gen_request(&request, t->path, t->host,
- t->cred, t->auth_mechanism, s->verb,
- s->service, s->service_url, len) < 0) {
+ 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)
- goto on_error;
-
- if (len && gitno_send(&t->socket, buffer, len, 0) < 0)
- goto on_error;
+ 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 = (char *)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:
@@ -478,6 +612,9 @@ 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);
+
git__free(s);
}
@@ -494,7 +631,7 @@ static int http_stream_alloc(http_subtransport *t,
s->parent.subtransport = &t->parent;
s->parent.read = http_stream_read;
- s->parent.write = http_stream_write;
+ s->parent.write = http_stream_write_single;
s->parent.free = http_stream_free;
*stream = (git_smart_subtransport_stream *)s;
@@ -537,14 +674,54 @@ static int http_uploadpack(
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 *smart_transport,
+ git_smart_subtransport *subtransport,
const char *url,
git_smart_service_t action)
{
- http_subtransport *t = (http_subtransport *)smart_transport;
- const char *default_port;
+ http_subtransport *t = (http_subtransport *)subtransport;
+ const char *default_port = NULL;
int flags = 0, ret;
if (!stream)
@@ -562,6 +739,9 @@ static int http_action(
t->use_ssl = 1;
}
+ if (!default_port)
+ return -1;
+
if ((ret = gitno_extract_host_and_port(&t->host, &t->port,
url, default_port)) < 0)
return ret;
@@ -569,7 +749,13 @@ static int http_action(
t->path = strchr(url, '/');
}
- if (!t->connected || !http_should_keep_alive(&t->parser)) {
+ if (!t->connected ||
+ !http_should_keep_alive(&t->parser) ||
+ !http_body_is_final(&t->parser)) {
+
+ if (t->socket.socket)
+ gitno_close(&t->socket);
+
if (t->use_ssl) {
int transport_flags;
@@ -588,9 +774,6 @@ static int http_action(
t->connected = 1;
}
- t->parse_finished = 0;
- t->parse_error = 0;
-
switch (action)
{
case GIT_SERVICE_UPLOADPACK_LS:
@@ -598,28 +781,53 @@ static int http_action(
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 void http_free(git_smart_subtransport *smart_transport)
+static int http_close(git_smart_subtransport *subtransport)
{
- http_subtransport *t = (http_subtransport *) smart_transport;
+ http_subtransport *t = (http_subtransport *) subtransport;
clear_parser_state(t);
- if (t->socket.socket)
+ 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;
}
- git__free(t->host);
- git__free(t->port);
+ if (t->host) {
+ git__free(t->host);
+ t->host = NULL;
+ }
+
+ if (t->port) {
+ git__free(t->port);
+ t->port = NULL;
+ }
+
+ return 0;
+}
+
+static void http_free(git_smart_subtransport *subtransport)
+{
+ http_subtransport *t = (http_subtransport *) subtransport;
+
+ http_close(subtransport);
+
git__free(t);
}
@@ -635,6 +843,7 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
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;
diff --git a/src/transports/local.c b/src/transports/local.c
index 51544416d..db9a08a57 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -26,6 +26,7 @@
typedef struct {
git_transport parent;
+ git_remote *owner;
char *url;
int direction;
int flags;
@@ -339,13 +340,11 @@ cleanup:
return error;
}
-static int local_is_connected(git_transport *transport, int *connected)
+static int local_is_connected(git_transport *transport)
{
transport_local *t = (transport_local *)transport;
- *connected = t->connected;
-
- return 0;
+ return t->connected;
}
static int local_read_flags(git_transport *transport, int *flags)
@@ -398,7 +397,7 @@ static void local_free(git_transport *transport)
* Public API *
**************/
-int git_transport_local(git_transport **out, void *param)
+int git_transport_local(git_transport **out, git_remote *owner, void *param)
{
transport_local *t;
@@ -419,6 +418,8 @@ int git_transport_local(git_transport **out, void *param)
t->parent.read_flags = local_read_flags;
t->parent.cancel = local_cancel;
+ t->owner = owner;
+
*out = (git_transport *) t;
return 0;
diff --git a/src/transports/smart.c b/src/transports/smart.c
index e8dbbef5c..00f8832e0 100644
--- a/src/transports/smart.c
+++ b/src/transports/smart.c
@@ -29,12 +29,18 @@ static int git_smart__recv_cb(gitno_buffer *buf)
return (int)(buf->offset - old_len);
}
-GIT_INLINE(void) git_smart__reset_stream(transport_smart *t)
+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(
@@ -63,8 +69,11 @@ static int git_smart__connect(
git_smart_subtransport_stream *stream;
int error;
git_pkt *pkt;
+ git_pkt_ref *first;
+ git_smart_service_t service;
- git_smart__reset_stream(t);
+ if (git_smart__reset_stream(t, true) < 0)
+ return -1;
t->url = git__strdup(url);
GITERR_CHECK_ALLOC(t->url);
@@ -73,55 +82,64 @@ static int git_smart__connect(
t->flags = flags;
t->cred_acquire_cb = cred_acquire_cb;
- if (GIT_DIRECTION_FETCH == direction)
- {
- if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 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);
- }
- }
+ 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;
+ }
- /* We now have loaded the refs. */
- t->have_refs = 1;
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
+ return error;
- if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0)
- return -1;
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
- if (t->rpc)
- git_smart__reset_stream(t);
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
- /* We're now logically connected. */
- t->connected = 1;
+ /* 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);
- return 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);
+ }
}
- else
- {
- giterr_set(GITERR_NET, "Push not implemented");
+
+ /* 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);
}
- return -1;
+ 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)
@@ -156,29 +174,55 @@ int git_smart__negotiation_step(git_transport *transport, void *data, size_t len
git_smart_subtransport_stream *stream;
int error;
- if (t->rpc)
- git_smart__reset_stream(t);
+ 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);
- if (GIT_DIRECTION_FETCH == t->direction) {
- if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
- return error;
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
- /* If this is a stateful implementation, the stream we get back should be the same */
- assert(t->rpc || t->current_stream == stream);
+ if ((error = stream->write(stream, (const char *)data, len)) < 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);
- if ((error = stream->write(stream, (const char *)data, len)) < 0)
- return error;
+ return 0;
+}
- gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+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;
- return 0;
+ if (GIT_DIRECTION_PUSH != t->direction) {
+ giterr_set(GITERR_NET, "This operation is only valid for push");
+ return -1;
}
- giterr_set(GITERR_NET, "Push not implemented");
- 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)
@@ -188,13 +232,11 @@ static void git_smart__cancel(git_transport *transport)
git_atomic_set(&t->cancelled, 1);
}
-static int git_smart__is_connected(git_transport *transport, int *connected)
+static int git_smart__is_connected(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
- *connected = t->connected;
-
- return 0;
+ return t->connected;
}
static int git_smart__read_flags(git_transport *transport, int *flags)
@@ -209,21 +251,37 @@ static int git_smart__read_flags(git_transport *transport, int *flags)
static int git_smart__close(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
-
- git_smart__reset_stream(t);
+ git_vector *refs = &t->refs;
+ git_vector *common = &t->common;
+ unsigned int i;
+ git_pkt *p;
+ int ret;
+
+ ret = git_smart__reset_stream(t, true);
+
+ 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);
+
+ if (t->url) {
+ git__free(t->url);
+ t->url = NULL;
+ }
t->connected = 0;
- return 0;
+ return ret;
}
static void git_smart__free(git_transport *transport)
{
transport_smart *t = (transport_smart *)transport;
- git_vector *refs = &t->refs;
- git_vector *common = &t->common;
- unsigned int i;
- git_pkt *p;
/* Make sure that the current stream is closed, if we have one. */
git_smart__close(transport);
@@ -231,21 +289,10 @@ static void git_smart__free(git_transport *transport)
/* Free the subtransport */
t->wrapped->free(t->wrapped);
- 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__free(t->url);
git__free(t);
}
-int git_transport_smart(git_transport **out, void *param)
+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;
@@ -262,11 +309,13 @@ int git_transport_smart(git_transport **out, void *param)
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, NULL) < 0) {
diff --git a/src/transports/smart.h b/src/transports/smart.h
index b37c4ba96..ea2784bb1 100644
--- a/src/transports/smart.h
+++ b/src/transports/smart.h
@@ -8,6 +8,7 @@
#include "vector.h"
#include "netops.h"
#include "buffer.h"
+#include "push.h"
#define GIT_SIDE_BAND_DATA 1
#define GIT_SIDE_BAND_PROGRESS 2
@@ -18,6 +19,8 @@
#define GIT_CAP_SIDE_BAND "side-band"
#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
#define GIT_CAP_INCLUDE_TAG "include-tag"
+#define GIT_CAP_DELETE_REFS "delete-refs"
+#define GIT_CAP_REPORT_STATUS "report-status"
enum git_pkt_type {
GIT_PKT_CMD,
@@ -31,6 +34,9 @@ enum git_pkt_type {
GIT_PKT_ERR,
GIT_PKT_DATA,
GIT_PKT_PROGRESS,
+ GIT_PKT_OK,
+ GIT_PKT_NG,
+ GIT_PKT_UNPACK,
};
/* Used for multi-ack */
@@ -85,19 +91,38 @@ typedef struct {
char error[GIT_FLEX_ARRAY];
} git_pkt_err;
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+} git_pkt_ok;
+
+typedef struct {
+ enum git_pkt_type type;
+ char *ref;
+ char *msg;
+} git_pkt_ng;
+
+typedef struct {
+ enum git_pkt_type type;
+ int unpack_ok;
+} git_pkt_unpack;
+
typedef struct transport_smart_caps {
int common:1,
ofs_delta:1,
multi_ack: 1,
side_band:1,
side_band_64k:1,
- include_tag:1;
+ include_tag:1,
+ delete_refs:1,
+ report_status:1;
} transport_smart_caps;
typedef void (*packetsize_cb)(size_t received, void *payload);
typedef struct {
git_transport parent;
+ git_remote *owner;
char *url;
git_cred_acquire_cb cred_acquire_cb;
int direction;
@@ -123,6 +148,7 @@ typedef struct {
/* smart_protocol.c */
int git_smart__store_refs(transport_smart *t, int flushes);
int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps);
+int git_smart__push(git_transport *transport, git_push *push);
int git_smart__negotiate_fetch(
git_transport *transport,
@@ -139,6 +165,7 @@ int git_smart__download_pack(
/* smart.c */
int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
+int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out);
/* smart_pkt.c */
int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c
index 26fc0e4aa..df9863728 100644
--- a/src/transports/smart_pkt.c
+++ b/src/transports/smart_pkt.c
@@ -30,7 +30,7 @@ static int flush_pkt(git_pkt **out)
{
git_pkt *pkt;
- pkt = git__malloc(sizeof(git_pkt));
+ pkt = (git_pkt *) git__malloc(sizeof(git_pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_FLUSH;
@@ -46,7 +46,7 @@ static int ack_pkt(git_pkt **out, const char *line, size_t len)
GIT_UNUSED(line);
GIT_UNUSED(len);
- pkt = git__calloc(1, sizeof(git_pkt_ack));
+ pkt = (git_pkt_ack *) git__calloc(1, sizeof(git_pkt_ack));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_ACK;
@@ -73,7 +73,7 @@ static int nak_pkt(git_pkt **out)
{
git_pkt *pkt;
- pkt = git__malloc(sizeof(git_pkt));
+ pkt = (git_pkt *) git__malloc(sizeof(git_pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_NAK;
@@ -86,7 +86,7 @@ static int pack_pkt(git_pkt **out)
{
git_pkt *pkt;
- pkt = git__malloc(sizeof(git_pkt));
+ pkt = (git_pkt *) git__malloc(sizeof(git_pkt));
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_PACK;
@@ -99,7 +99,7 @@ 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);
+ pkt = (git_pkt_comment *) git__malloc(sizeof(git_pkt_comment) + len + 1);
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_COMMENT;
@@ -118,7 +118,7 @@ static int err_pkt(git_pkt **out, const char *line, size_t len)
/* Remove "ERR " from the line */
line += 4;
len -= 4;
- pkt = git__malloc(sizeof(git_pkt_err) + len + 1);
+ pkt = (git_pkt_err *) git__malloc(sizeof(git_pkt_err) + len + 1);
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_ERR;
@@ -136,7 +136,7 @@ static int data_pkt(git_pkt **out, const char *line, size_t len)
line++;
len--;
- pkt = git__malloc(sizeof(git_pkt_data) + len);
+ pkt = (git_pkt_data *) git__malloc(sizeof(git_pkt_data) + len);
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_DATA;
@@ -154,7 +154,7 @@ static int progress_pkt(git_pkt **out, const char *line, size_t len)
line++;
len--;
- pkt = git__malloc(sizeof(git_pkt_progress) + len);
+ pkt = (git_pkt_progress *) git__malloc(sizeof(git_pkt_progress) + len);
GITERR_CHECK_ALLOC(pkt);
pkt->type = GIT_PKT_PROGRESS;
@@ -174,7 +174,7 @@ 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));
+ pkt = (git_pkt_ref *) git__malloc(sizeof(git_pkt_ref));
GITERR_CHECK_ALLOC(pkt);
memset(pkt, 0x0, sizeof(git_pkt_ref));
@@ -196,7 +196,7 @@ static int ref_pkt(git_pkt **out, const char *line, size_t len)
if (line[len - 1] == '\n')
--len;
- pkt->head.name = git__malloc(len + 1);
+ pkt->head.name = (char *) git__malloc(len + 1);
GITERR_CHECK_ALLOC(pkt->head.name);
memcpy(pkt->head.name, line, len);
@@ -214,6 +214,83 @@ error_out:
return error;
}
+static int ok_pkt(git_pkt **out, const char *line, size_t len)
+{
+ git_pkt_ok *pkt;
+ const char *ptr;
+
+ pkt = (git_pkt_ok *) 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 = (char *) 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_pkt_ng *) 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 = (char *) 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 = (char *) 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_pkt_unpack *) 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];
@@ -311,6 +388,12 @@ int git_pkt_parse_line(
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);
@@ -326,6 +409,17 @@ void git_pkt_free(git_pkt *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);
}
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 99d34e23b..7a604aaff 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -4,9 +4,14 @@
* 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)
@@ -18,6 +23,11 @@ int git_smart__store_refs(transport_smart *t, int flushes)
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);
@@ -71,37 +81,43 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
if (*ptr == ' ')
ptr++;
- if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
+ 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)) {
+ 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)) {
+ 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)) {
+ 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)) {
+ 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, ' ');
}
@@ -471,7 +487,8 @@ on_success:
error = 0;
on_error:
- writepack->free(writepack);
+ if (writepack)
+ writepack->free(writepack);
/* Trailing execution of progress_cb, if necessary */
if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes)
@@ -479,3 +496,218 @@ on_error:
return error;
}
+
+static int gen_pktline(git_buf *buf, git_push *push)
+{
+ git_remote_head *head;
+ push_spec *spec;
+ unsigned int i, j, len;
+ char hex[41]; hex[40] = '\0';
+
+ git_vector_foreach(&push->specs, i, spec) {
+ len = 2*GIT_OID_HEXSZ + 7;
+
+ if (i == 0) {
+ len +=1; /* '\0' */
+ if (push->report_status)
+ len += strlen(GIT_CAP_REPORT_STATUS);
+ }
+
+ if (spec->lref) {
+ len += spec->rref ? strlen(spec->rref) : strlen(spec->lref);
+
+ if (git_oid_iszero(&spec->roid)) {
+
+ /*
+ * Create remote reference
+ */
+ git_oid_fmt(hex, &spec->loid);
+ git_buf_printf(buf, "%04x%s %s %s", len,
+ GIT_OID_HEX_ZERO, hex,
+ spec->rref ? spec->rref : spec->lref);
+
+ } else {
+
+ /*
+ * Update remote reference
+ */
+ git_oid_fmt(hex, &spec->roid);
+ git_buf_printf(buf, "%04x%s ", len, hex);
+
+ git_oid_fmt(hex, &spec->loid);
+ git_buf_printf(buf, "%s %s", hex,
+ spec->rref ? spec->rref : spec->lref);
+ }
+ } else {
+ /*
+ * Delete remote reference
+ */
+ git_vector_foreach(&push->remote->refs, j, head) {
+ if (!strcmp(spec->rref, head->name)) {
+ len += strlen(spec->rref);
+
+ git_oid_fmt(hex, &head->oid);
+ git_buf_printf(buf, "%04x%s %s %s", len,
+ hex, GIT_OID_HEX_ZERO, head->name);
+
+ break;
+ }
+ }
+ }
+
+ if (i == 0) {
+ git_buf_putc(buf, '\0');
+ if (push->report_status)
+ git_buf_printf(buf, GIT_CAP_REPORT_STATUS);
+ }
+
+ git_buf_putc(buf, '\n');
+ }
+ git_buf_puts(buf, "0000");
+ return git_buf_oom(buf) ? -1 : 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);
+
+ if (pkt->type == GIT_PKT_OK) {
+ push_status *status = (push_status *) git__malloc(sizeof(push_status));
+ GITERR_CHECK_ALLOC(status);
+ status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
+ status->msg = NULL;
+ git_pkt_free(pkt);
+ if (git_vector_insert(&push->status, status) < 0) {
+ git__free(status);
+ return -1;
+ }
+ continue;
+ }
+
+ if (pkt->type == GIT_PKT_NG) {
+ push_status *status = (push_status *) git__malloc(sizeof(push_status));
+ GITERR_CHECK_ALLOC(status);
+ status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
+ status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
+ git_pkt_free(pkt);
+ if (git_vector_insert(&push->status, status) < 0) {
+ git__free(status);
+ return -1;
+ }
+ continue;
+ }
+
+ if (pkt->type == GIT_PKT_UNPACK) {
+ push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
+ git_pkt_free(pkt);
+ continue;
+ }
+
+ if (pkt->type == GIT_PKT_FLUSH) {
+ git_pkt_free(pkt);
+ return 0;
+ }
+
+ git_pkt_free(pkt);
+ giterr_set(GITERR_NET, "report-status: protocol error");
+ return -1;
+ }
+}
+
+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;
+ char *url = NULL;
+ 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 we updated at least one ref, then we need to re-acquire the list of
+ * refs so the caller can call git_remote_update_tips afterward. TODO: Use
+ * the data from the push report to do this without another network call */
+ if (push->specs.length) {
+ git_cred_acquire_cb cred_cb = t->cred_acquire_cb;
+ int flags = t->flags;
+
+ url = git__strdup(t->url);
+
+ if (!url || t->parent.close(&t->parent) < 0 ||
+ t->parent.connect(&t->parent, url, cred_cb, GIT_DIRECTION_PUSH, flags))
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ git__free(url);
+ git_buf_free(&pktline);
+
+ return error;
+}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index f3abe8598..fa1bbb7e3 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -13,25 +13,37 @@
#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
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
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 *basic_authtype = L"Basic";
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;
@@ -48,8 +60,13 @@ typedef struct {
const char *service_url;
const wchar_t *verb;
HINTERNET request;
+ char *chunk_buffer;
+ unsigned chunk_buffer_len;
+ HANDLE post_body;
+ DWORD post_body_len;
unsigned sent_request : 1,
- received_response : 1;
+ received_response : 1,
+ chunked : 1;
} winhttp_stream;
typedef struct {
@@ -126,9 +143,11 @@ 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 url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN];
wchar_t *types[] = { L"*/*", NULL };
BOOL peerdist = FALSE;
+ int error = -1;
/* Prepare URL */
git_buf_printf(&buf, "%s%s", t->path, s->service_url);
@@ -153,6 +172,36 @@ static int winhttp_stream_connect(winhttp_stream *s)
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;
+ size_t wide_len;
+
+ git__utf8_to_16(url, GIT_WIN_PATH, proxy_url);
+
+ wide_len = wcslen(url);
+
+ /* Strip any trailing forward slash on the proxy URL;
+ * WinHTTP doesn't like it if one is present */
+ if (L'/' == url[wide_len - 1])
+ url[wide_len - 1] = L'\0';
+
+ proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY;
+ proxy_info.lpszProxy = url;
+ 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");
+ goto on_error;
+ }
+ }
+
/* 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 */
@@ -205,11 +254,12 @@ static int winhttp_stream_connect(winhttp_stream *s)
/* We've done everything up to calling WinHttpSendRequest. */
- return 0;
+ error = 0;
on_error:
+ git__free(proxy_url);
git_buf_free(&buf);
- return -1;
+ return error;
}
static int parse_unauthorized_response(
@@ -217,57 +267,65 @@ static int parse_unauthorized_response(
int *allowed_types,
int *auth_mechanism)
{
- DWORD index, buf_size, last_error;
- int error = 0;
- wchar_t *buf = NULL;
+ DWORD supported, first, target;
*allowed_types = 0;
+ *auth_mechanism = 0;
- for (index = 0; ; index++) {
- /* Make a first call to ask for the size of the buffer to allocate
- * to hold the WWW-Authenticate header */
- if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE,
- WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER,
- &buf_size, &index))
- {
- last_error = GetLastError();
-
- if (ERROR_WINHTTP_HEADER_NOT_FOUND == last_error) {
- /* End of enumeration */
- break;
- } else if (ERROR_INSUFFICIENT_BUFFER == last_error) {
- git__free(buf);
- buf = (wchar_t *)git__malloc(buf_size);
-
- if (!buf) {
- error = -1;
- break;
- }
- } else {
- giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header");
- error = -1;
- break;
- }
- }
+ /* 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;
+ }
- /* Actually receive the data into our now-allocated buffer */
- if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE,
- WINHTTP_HEADER_NAME_BY_INDEX, buf,
- &buf_size, &index)) {
- giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header");
- error = -1;
- break;
- }
+ if (WINHTTP_AUTH_SCHEME_BASIC & supported) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
+ }
- if (!wcsncmp(buf, basic_authtype, 5) &&
- (buf[5] == L'\0' || buf[5] == L' ')) {
- *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), git_buf_len(&buf),
+ &bytes_written)) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_OS, "Failed to write chunk header");
+ return -1;
}
- git__free(buf);
- return error;
+ git_buf_free(&buf);
+
+ /* Chunk body */
+ if (!WinHttpWriteData(request,
+ buffer, 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(
@@ -285,22 +343,84 @@ replay:
if (!s->request && winhttp_stream_connect(s) < 0)
return -1;
- if (!s->sent_request &&
- !WinHttpSendRequest(s->request,
- WINHTTP_NO_ADDITIONAL_HEADERS, 0,
- WINHTTP_NO_REQUEST_DATA, 0,
- 0, 0)) {
- giterr_set(GITERR_OS, "Failed to send request");
- return -1;
- }
-
- s->sent_request = 1;
-
if (!s->received_response) {
- DWORD status_code, status_code_length, content_type_length;
+ 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 = (char *)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;
@@ -376,7 +496,7 @@ replay:
if (!WinHttpReadData(s->request,
(LPVOID)buffer,
- (DWORD)buf_size,
+ buf_size,
&dw_bytes_read))
{
giterr_set(GITERR_OS, "Failed to read data");
@@ -388,7 +508,7 @@ replay:
return 0;
}
-static int winhttp_stream_write(
+static int winhttp_stream_write_single(
git_smart_subtransport_stream *stream,
const char *buffer,
size_t len)
@@ -400,10 +520,13 @@ static int winhttp_stream_write(
if (!s->request && winhttp_stream_connect(s) < 0)
return -1;
- /* Since we have to write the Content-Length header up front, we're
- * basically limited to a single call to write() per request. */
- if (!s->sent_request &&
- !WinHttpSendRequest(s->request,
+ /* 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)) {
@@ -417,12 +540,190 @@ static int winhttp_stream_write(
(LPCVOID)buffer,
(DWORD)len,
&bytes_written)) {
- giterr_set(GITERR_OS, "Failed to send request");
+ giterr_set(GITERR_OS, "Failed to write data");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ return 0;
+}
+
+static int put_uuid_string(LPWSTR buffer, DWORD buffer_len_cch)
+{
+ UUID uuid;
+ RPC_STATUS status = UuidCreate(&uuid);
+ int 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 insufficient to generate temp file name");
+ return -1;
+ }
+
+ result = wsprintfW(buffer, 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 (result != UUID_LENGTH_CCH) {
+ 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)
+{
+ int len;
+
+ if (!GetTempPathW(buffer_len_cch, buffer)) {
+ giterr_set(GITERR_OS, "Failed to get temp path");
+ return -1;
+ }
+
+ len = wcslen(buffer);
+
+ /* 1 prefix character for the backslash, 1 postfix for
+ * the null terminator */
+ if (buffer_len_cch - len < 1 + UUID_LENGTH_CCH + 1) {
+ giterr_set(GITERR_NET, "Buffer insufficient to generate temp file name");
+ return -1;
+ }
+
+ if (buffer[len - 1] != '\\')
+ buffer[len++] = '\\';
+
+ if (put_uuid_string(&buffer[len], UUID_LENGTH_CCH + 1) < 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, 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, len);
+
+ if (!s->chunk_buffer)
+ s->chunk_buffer = (char *)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 = len;
+ }
+ }
+ }
+
return 0;
}
@@ -430,6 +731,16 @@ 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) {
WinHttpCloseHandle(s->request);
s->request = NULL;
@@ -438,7 +749,7 @@ static void winhttp_stream_free(git_smart_subtransport_stream *stream)
git__free(s);
}
-static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_stream **stream)
+static int winhttp_stream_alloc(winhttp_subtransport *t, winhttp_stream **stream)
{
winhttp_stream *s;
@@ -450,10 +761,11 @@ static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_
s->parent.subtransport = &t->parent;
s->parent.read = winhttp_stream_read;
- s->parent.write = winhttp_stream_write;
+ s->parent.write = winhttp_stream_write_single;
s->parent.free = winhttp_stream_free;
- *stream = (git_smart_subtransport_stream *)s;
+ *stream = s;
+
return 0;
}
@@ -520,20 +832,8 @@ static int winhttp_connect(
static int winhttp_uploadpack_ls(
winhttp_subtransport *t,
- const char *url,
- git_smart_subtransport_stream **stream)
+ winhttp_stream *s)
{
- winhttp_stream *s;
-
- if (!t->connection &&
- winhttp_connect(t, url) < 0)
- return -1;
-
- if (winhttp_stream_alloc(t, stream) < 0)
- return -1;
-
- s = (winhttp_stream *)*stream;
-
s->service = upload_pack_service;
s->service_url = upload_pack_ls_service_url;
s->verb = get_verb;
@@ -543,22 +843,41 @@ static int winhttp_uploadpack_ls(
static int winhttp_uploadpack(
winhttp_subtransport *t,
- const char *url,
- git_smart_subtransport_stream **stream)
+ winhttp_stream *s)
{
- winhttp_stream *s;
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
- if (!t->connection &&
- winhttp_connect(t, url) < 0)
- return -1;
+ return 0;
+}
- if (winhttp_stream_alloc(t, stream) < 0)
- return -1;
+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;
- s = (winhttp_stream *)*stream;
+ return 0;
+}
- s->service = upload_pack_service;
- s->service_url = upload_pack_service_url;
+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 = LOBYTE(LOWORD(GetVersion())) >= 6;
+
+ 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;
@@ -566,11 +885,20 @@ static int winhttp_uploadpack(
static int winhttp_action(
git_smart_subtransport_stream **stream,
- git_smart_subtransport *smart_transport,
+ git_smart_subtransport *subtransport,
const char *url,
git_smart_service_t action)
{
- winhttp_subtransport *t = (winhttp_subtransport *)smart_transport;
+ 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;
@@ -578,22 +906,45 @@ static int winhttp_action(
switch (action)
{
case GIT_SERVICE_UPLOADPACK_LS:
- return winhttp_uploadpack_ls(t, url, stream);
+ ret = winhttp_uploadpack_ls(t, s);
+ break;
case GIT_SERVICE_UPLOADPACK:
- return winhttp_uploadpack(t, url, stream);
+ 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);
}
- *stream = NULL;
- return -1;
+ if (!ret)
+ *stream = &s->parent;
+
+ return ret;
}
-static void winhttp_free(git_smart_subtransport *smart_transport)
+static int winhttp_close(git_smart_subtransport *subtransport)
{
- winhttp_subtransport *t = (winhttp_subtransport *) smart_transport;
-
- git__free(t->host);
- git__free(t->port);
+ 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->cred) {
t->cred->free(t->cred);
@@ -601,15 +952,32 @@ static void winhttp_free(git_smart_subtransport *smart_transport)
}
if (t->connection) {
- WinHttpCloseHandle(t->connection);
+ if (!WinHttpCloseHandle(t->connection)) {
+ giterr_set(GITERR_OS, "Unable to close connection");
+ ret = -1;
+ }
+
t->connection = NULL;
}
if (t->session) {
- WinHttpCloseHandle(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);
}
@@ -625,6 +993,7 @@ int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *own
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;
diff --git a/tests-clar/network/fetch.c b/tests-clar/network/fetch.c
index 81a0eed25..924998448 100644
--- a/tests-clar/network/fetch.c
+++ b/tests-clar/network/fetch.c
@@ -48,8 +48,8 @@ static void do_fetch(const char *url, int flag, int n)
git_remote_set_autotag(remote, flag);
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
cl_git_pass(git_remote_download(remote, progress, &bytes_received));
- git_remote_disconnect(remote);
cl_git_pass(git_remote_update_tips(remote));
+ git_remote_disconnect(remote);
cl_assert_equal_i(counter, n);
cl_assert(bytes_received > 0);
diff --git a/tests-clar/network/push.c b/tests-clar/network/push.c
new file mode 100644
index 000000000..acc376de7
--- /dev/null
+++ b/tests-clar/network/push.c
@@ -0,0 +1,518 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "posix.h"
+#include "vector.h"
+#include "../submodule/submodule_helpers.h"
+#include "push_util.h"
+
+CL_IN_CATEGORY("network")
+
+static git_repository *_repo;
+
+static char *_remote_url;
+static char *_remote_user;
+static char *_remote_pass;
+
+static git_remote *_remote;
+static record_callbacks_data _record_cbs_data = {{ 0 }};
+static git_remote_callbacks _record_cbs = RECORD_CALLBACKS_INIT(&_record_cbs_data);
+
+static git_oid _oid_b6;
+static git_oid _oid_b5;
+static git_oid _oid_b4;
+static git_oid _oid_b3;
+static git_oid _oid_b2;
+static git_oid _oid_b1;
+
+/* git_oid *oid, git_repository *repo, (string literal) blob */
+#define CREATE_BLOB(oid, repo, blob) git_blob_create_frombuffer(oid, repo, blob, sizeof(blob) - 1)
+
+static int cred_acquire_cb(git_cred **cred, const char *url, unsigned int allowed_types)
+{
+ GIT_UNUSED(url);
+
+ if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 ||
+ git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass) < 0)
+ return -1;
+
+ return 0;
+}
+
+typedef struct {
+ const char *ref;
+ const char *msg;
+} push_status;
+
+/**
+ * git_push_status_foreach callback that records status entries.
+ * @param data (git_vector *) of push_status instances
+ */
+static int record_push_status_cb(const char *ref, const char *msg, void *data)
+{
+ git_vector *statuses = (git_vector *)data;
+ push_status *s;
+
+ cl_assert(s = git__malloc(sizeof(*s)));
+ s->ref = ref;
+ s->msg = msg;
+
+ git_vector_insert(statuses, s);
+
+ return 0;
+}
+
+static void do_verify_push_status(git_push *push, const push_status expected[], const size_t expected_len)
+{
+ git_vector actual = GIT_VECTOR_INIT;
+ push_status *iter;
+ bool failed = false;
+ size_t i;
+
+ git_push_status_foreach(push, record_push_status_cb, &actual);
+
+ if (expected_len != actual.length)
+ failed = true;
+ else
+ git_vector_foreach(&actual, i, iter)
+ if (strcmp(expected[i].ref, iter->ref) ||
+ (expected[i].msg && strcmp(expected[i].msg, iter->msg))) {
+ failed = true;
+ break;
+ }
+
+ if (failed) {
+ git_buf msg = GIT_BUF_INIT;
+
+ git_buf_puts(&msg, "Expected and actual push statuses differ:\nEXPECTED:\n");
+
+ for(i = 0; i < expected_len; i++) {
+ git_buf_printf(&msg, "%s: %s\n",
+ expected[i].ref,
+ expected[i].msg ? expected[i].msg : "<NULL>");
+ }
+
+ git_buf_puts(&msg, "\nACTUAL:\n");
+
+ git_vector_foreach(&actual, i, iter)
+ git_buf_printf(&msg, "%s: %s\n", iter->ref, iter->msg);
+
+ cl_fail(git_buf_cstr(&msg));
+
+ git_buf_free(&msg);
+ }
+
+ git_vector_foreach(&actual, i, iter)
+ git__free(iter);
+
+ git_vector_free(&actual);
+}
+
+/**
+ * Verifies that after git_push_finish(), refs on a remote have the expected
+ * names, oids, and order.
+ *
+ * @param remote remote to verify
+ * @param expected_refs expected remote refs after push
+ * @param expected_refs_len length of expected_refs
+ */
+static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
+{
+ git_vector actual_refs = GIT_VECTOR_INIT;
+
+ git_remote_ls(remote, record_ref_cb, &actual_refs);
+ verify_remote_refs(&actual_refs, expected_refs, expected_refs_len);
+
+ git_vector_free(&actual_refs);
+}
+
+void test_network_push__initialize(void)
+{
+ git_vector delete_specs = GIT_VECTOR_INIT;
+ size_t i;
+ char *curr_del_spec;
+
+ _repo = cl_git_sandbox_init("push_src");
+
+ cl_fixture_sandbox("testrepo.git");
+ cl_rename("push_src/submodule/.gitted", "push_src/submodule/.git");
+
+ rewrite_gitmodules(git_repository_workdir(_repo));
+
+ /* git log --format=oneline --decorate --graph
+ * *-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, b6) merge b3, b4, and b5 to b6
+ * |\ \
+ * | | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git'
+ * | * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt
+ * | |/
+ * * | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt
+ * |/
+ * * a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt
+ * * 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt
+ */
+ git_oid_fromstr(&_oid_b6, "951bbbb90e2259a4c8950db78946784fb53fcbce");
+ git_oid_fromstr(&_oid_b5, "fa38b91f199934685819bea316186d8b008c52a2");
+ git_oid_fromstr(&_oid_b4, "27b7ce66243eb1403862d05f958c002312df173d");
+ git_oid_fromstr(&_oid_b3, "d9b63a88223d8367516f50bd131a5f7349b7f3e4");
+ git_oid_fromstr(&_oid_b2, "a78705c3b2725f931d3ee05348d83cc26700f247");
+ git_oid_fromstr(&_oid_b1, "a78705c3b2725f931d3ee05348d83cc26700f247");
+
+ /* Remote URL environment variable must be set. User and password are optional. */
+ _remote_url = cl_getenv("GITTEST_REMOTE_URL");
+ _remote_user = cl_getenv("GITTEST_REMOTE_USER");
+ _remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
+ _remote = NULL;
+
+ if (_remote_url) {
+ cl_git_pass(git_remote_add(&_remote, _repo, "test", _remote_url));
+
+ git_remote_set_cred_acquire_cb(_remote, cred_acquire_cb);
+ record_callbacks_data_clear(&_record_cbs_data);
+ git_remote_set_callbacks(_remote, &_record_cbs);
+
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
+
+ /* Clean up previously pushed branches. Fails if receive.denyDeletes is
+ * set on the remote. Also, on Git 1.7.0 and newer, you must run
+ * 'git config receive.denyDeleteCurrent ignore' in the remote repo in
+ * order to delete the remote branch pointed to by HEAD (usually master).
+ * See: https://raw.github.com/git/git/master/Documentation/RelNotes/1.7.0.txt
+ */
+ cl_git_pass(git_remote_ls(_remote, delete_ref_cb, &delete_specs));
+ if (delete_specs.length) {
+ git_push *push;
+
+ cl_git_pass(git_push_new(&push, _remote));
+
+ git_vector_foreach(&delete_specs, i, curr_del_spec) {
+ git_push_add_refspec(push, curr_del_spec);
+ git__free(curr_del_spec);
+ }
+
+ cl_git_pass(git_push_finish(push));
+ git_push_free(push);
+ }
+
+ git_remote_disconnect(_remote);
+ git_vector_free(&delete_specs);
+
+ /* Now that we've deleted everything, fetch from the remote */
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(_remote, NULL, NULL));
+ cl_git_pass(git_remote_update_tips(_remote));
+ git_remote_disconnect(_remote);
+ } else
+ printf("GITTEST_REMOTE_URL unset; skipping push test\n");
+}
+
+void test_network_push__cleanup(void)
+{
+ if (_remote)
+ git_remote_free(_remote);
+
+ record_callbacks_data_clear(&_record_cbs_data);
+
+ cl_fixture_cleanup("testrepo.git");
+ cl_git_sandbox_cleanup();
+}
+
+/**
+ * Calls push and relists refs on remote to verify success.
+ *
+ * @param refspecs refspecs to push
+ * @param refspecs_len length of refspecs
+ * @param expected_refs expected remote refs after push
+ * @param expected_refs_len length of expected_refs
+ * @param expected_ret expected return value from git_push_finish()
+ */
+static void do_push(const char *refspecs[], size_t refspecs_len,
+ push_status expected_statuses[], size_t expected_statuses_len,
+ expected_ref expected_refs[], size_t expected_refs_len, int expected_ret)
+{
+ git_push *push;
+ size_t i;
+ int ret;
+
+ if (_remote) {
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
+
+ cl_git_pass(git_push_new(&push, _remote));
+
+ for (i = 0; i < refspecs_len; i++)
+ cl_git_pass(git_push_add_refspec(push, refspecs[i]));
+
+ if (expected_ret < 0) {
+ cl_git_fail(ret = git_push_finish(push));
+ cl_assert_equal_i(0, git_push_unpack_ok(push));
+ }
+ else {
+ cl_git_pass(ret = git_push_finish(push));
+ cl_assert_equal_i(1, git_push_unpack_ok(push));
+ }
+
+ do_verify_push_status(push, expected_statuses, expected_statuses_len);
+
+ cl_assert_equal_i(expected_ret, ret);
+
+ git_push_free(push);
+
+ verify_refs(_remote, expected_refs, expected_refs_len);
+
+ cl_git_pass(git_remote_update_tips(_remote));
+
+ git_remote_disconnect(_remote);
+ }
+}
+
+/* Call push_finish() without ever calling git_push_add_refspec() */
+void test_network_push__noop(void)
+{
+ do_push(NULL, 0, NULL, 0, NULL, 0, 0);
+}
+
+void test_network_push__b1(void)
+{
+ const char *specs[] = { "refs/heads/b1:refs/heads/b1" };
+ push_status exp_stats[] = { { "refs/heads/b1", NULL } };
+ expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0);
+}
+
+void test_network_push__b2(void)
+{
+ const char *specs[] = { "refs/heads/b2:refs/heads/b2" };
+ push_status exp_stats[] = { { "refs/heads/b2", NULL } };
+ expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0);
+}
+
+void test_network_push__b3(void)
+{
+ const char *specs[] = { "refs/heads/b3:refs/heads/b3" };
+ push_status exp_stats[] = { { "refs/heads/b3", NULL } };
+ expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0);
+}
+
+void test_network_push__b4(void)
+{
+ const char *specs[] = { "refs/heads/b4:refs/heads/b4" };
+ push_status exp_stats[] = { { "refs/heads/b4", NULL } };
+ expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0);
+}
+
+void test_network_push__b5(void)
+{
+ const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
+ push_status exp_stats[] = { { "refs/heads/b5", NULL } };
+ expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0);
+}
+
+void test_network_push__multi(void)
+{
+ const char *specs[] = {
+ "refs/heads/b1:refs/heads/b1",
+ "refs/heads/b2:refs/heads/b2",
+ "refs/heads/b3:refs/heads/b3",
+ "refs/heads/b4:refs/heads/b4",
+ "refs/heads/b5:refs/heads/b5"
+ };
+ push_status exp_stats[] = {
+ { "refs/heads/b1", NULL },
+ { "refs/heads/b2", NULL },
+ { "refs/heads/b3", NULL },
+ { "refs/heads/b4", NULL },
+ { "refs/heads/b5", NULL }
+ };
+ expected_ref exp_refs[] = {
+ { "refs/heads/b1", &_oid_b1 },
+ { "refs/heads/b2", &_oid_b2 },
+ { "refs/heads/b3", &_oid_b3 },
+ { "refs/heads/b4", &_oid_b4 },
+ { "refs/heads/b5", &_oid_b5 }
+ };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0);
+}
+
+void test_network_push__implicit_tgt(void)
+{
+ const char *specs1[] = { "refs/heads/b1:" };
+ push_status exp_stats1[] = { { "refs/heads/b1", NULL } };
+ expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } };
+
+ const char *specs2[] = { "refs/heads/b2:" };
+ push_status exp_stats2[] = { { "refs/heads/b2", NULL } };
+ expected_ref exp_refs2[] = {
+ { "refs/heads/b1", &_oid_b1 },
+ { "refs/heads/b2", &_oid_b2 }
+ };
+
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0);
+ do_push(specs2, ARRAY_SIZE(specs2),
+ exp_stats2, ARRAY_SIZE(exp_stats2),
+ exp_refs2, ARRAY_SIZE(exp_refs2), 0);
+}
+
+void test_network_push__fast_fwd(void)
+{
+ /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */
+
+ const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" };
+ push_status exp_stats_init[] = { { "refs/heads/b1", NULL } };
+ expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } };
+
+ const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" };
+ push_status exp_stats_ff[] = { { "refs/heads/b1", NULL } };
+ expected_ref exp_refs_ff[] = { { "refs/heads/b1", &_oid_b6 } };
+
+ /* Do a force push to reset b1 in target back to _oid_b1 */
+ const char *specs_reset[] = { "+refs/heads/b1:refs/heads/b1" };
+ /* Force should have no effect on a fast forward push */
+ const char *specs_ff_force[] = { "+refs/heads/b6:refs/heads/b1" };
+
+ do_push(specs_init, ARRAY_SIZE(specs_init),
+ exp_stats_init, ARRAY_SIZE(exp_stats_init),
+ exp_refs_init, ARRAY_SIZE(exp_refs_init), 0);
+
+ do_push(specs_ff, ARRAY_SIZE(specs_ff),
+ exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
+ exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0);
+
+ do_push(specs_reset, ARRAY_SIZE(specs_reset),
+ exp_stats_init, ARRAY_SIZE(exp_stats_init),
+ exp_refs_init, ARRAY_SIZE(exp_refs_init), 0);
+
+ do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force),
+ exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
+ exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0);
+}
+
+void test_network_push__force(void)
+{
+ const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"};
+ push_status exp_stats1[] = { { "refs/heads/tgt", NULL } };
+ expected_ref exp_refs1[] = { { "refs/heads/tgt", &_oid_b3 } };
+
+ const char *specs2[] = {"refs/heads/b4:refs/heads/tgt"};
+
+ const char *specs2_force[] = {"+refs/heads/b4:refs/heads/tgt"};
+ push_status exp_stats2_force[] = { { "refs/heads/tgt", NULL } };
+ expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } };
+
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0);
+
+ do_push(specs2, ARRAY_SIZE(specs2),
+ NULL, 0,
+ exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD);
+
+ /* Non-fast-forward update with force should pass. */
+ do_push(specs2_force, ARRAY_SIZE(specs2_force),
+ exp_stats2_force, ARRAY_SIZE(exp_stats2_force),
+ exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0);
+}
+
+void test_network_push__delete(void)
+{
+ const char *specs1[] = {
+ "refs/heads/b1:refs/heads/tgt1",
+ "refs/heads/b1:refs/heads/tgt2"
+ };
+ push_status exp_stats1[] = {
+ { "refs/heads/tgt1", NULL },
+ { "refs/heads/tgt2", NULL }
+ };
+ expected_ref exp_refs1[] = {
+ { "refs/heads/tgt1", &_oid_b1 },
+ { "refs/heads/tgt2", &_oid_b1 }
+ };
+
+ const char *specs_del_fake[] = { ":refs/heads/fake" };
+ /* Force has no effect for delete. */
+ const char *specs_del_fake_force[] = { "+:refs/heads/fake" };
+
+ const char *specs_delete[] = { ":refs/heads/tgt1" };
+ push_status exp_stats_delete[] = { { "refs/heads/tgt1", NULL } };
+ expected_ref exp_refs_delete[] = { { "refs/heads/tgt2", &_oid_b1 } };
+ /* Force has no effect for delete. */
+ const char *specs_delete_force[] = { "+:refs/heads/tgt1" };
+
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0);
+
+ /* Deleting a non-existent branch should fail before the request is sent to
+ * the server because the client cannot find the old oid for the ref.
+ */
+ do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake),
+ NULL, 0,
+ exp_refs1, ARRAY_SIZE(exp_refs1), -1);
+ do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force),
+ NULL, 0,
+ exp_refs1, ARRAY_SIZE(exp_refs1), -1);
+
+ /* Delete one of the pushed branches. */
+ do_push(specs_delete, ARRAY_SIZE(specs_delete),
+ exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
+ exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0);
+
+ /* Re-push branches and retry delete with force. */
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0);
+ do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force),
+ exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
+ exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0);
+}
+
+void test_network_push__bad_refspecs(void)
+{
+ /* All classes of refspecs that should be rejected by
+ * git_push_add_refspec() should go in this test.
+ */
+ git_push *push;
+
+ if (_remote) {
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
+ cl_git_pass(git_push_new(&push, _remote));
+
+ /* Unexpanded branch names not supported */
+ cl_git_fail(git_push_add_refspec(push, "b6:b6"));
+
+ git_push_free(push);
+ }
+}
+
+void test_network_push__expressions(void)
+{
+ /* TODO: Expressions in refspecs doesn't actually work yet */
+ const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" };
+
+ const char *specs_right_expr[] = { "refs/heads/b2:refs/heads/b2~1" };
+ push_status exp_stats_right_expr[] = { { "refs/heads/b2~1", "funny refname" } };
+
+ /* TODO: Find a more precise way of checking errors than a exit code of -1. */
+ do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr),
+ NULL, 0,
+ NULL, 0, -1);
+
+ do_push(specs_right_expr, ARRAY_SIZE(specs_right_expr),
+ exp_stats_right_expr, ARRAY_SIZE(exp_stats_right_expr),
+ NULL, 0, 0);
+}
diff --git a/tests-clar/network/push_util.c b/tests-clar/network/push_util.c
new file mode 100644
index 000000000..2e457844d
--- /dev/null
+++ b/tests-clar/network/push_util.c
@@ -0,0 +1,126 @@
+
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "vector.h"
+#include "push_util.h"
+
+const git_oid OID_ZERO = {{ 0 }};
+
+void updated_tip_free(updated_tip *t)
+{
+ git__free(t->name);
+ git__free(t->old_oid);
+ git__free(t->new_oid);
+ git__free(t);
+}
+
+void record_callbacks_data_clear(record_callbacks_data *data)
+{
+ size_t i;
+ updated_tip *tip;
+
+ git_vector_foreach(&data->updated_tips, i, tip)
+ updated_tip_free(tip);
+
+ git_vector_free(&data->updated_tips);
+}
+
+int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
+{
+ updated_tip *t;
+ record_callbacks_data *record_data = (record_callbacks_data *)data;
+
+ cl_assert(t = git__malloc(sizeof(*t)));
+
+ cl_assert(t->name = git__strdup(refname));
+ cl_assert(t->old_oid = git__malloc(sizeof(*t->old_oid)));
+ git_oid_cpy(t->old_oid, a);
+
+ cl_assert(t->new_oid = git__malloc(sizeof(*t->new_oid)));
+ git_oid_cpy(t->new_oid, b);
+
+ git_vector_insert(&record_data->updated_tips, t);
+
+ return 0;
+}
+
+int delete_ref_cb(git_remote_head *head, void *payload)
+{
+ git_vector *delete_specs = (git_vector *)payload;
+ git_buf del_spec = GIT_BUF_INIT;
+
+ /* Ignore malformed ref names (which also saves us from tag^{} */
+ if (!git_reference_is_valid_name(head->name))
+ return 0;
+
+ /* Create a refspec that deletes a branch in the remote */
+ if (strcmp(head->name, "refs/heads/master")) {
+ cl_git_pass(git_buf_putc(&del_spec, ':'));
+ cl_git_pass(git_buf_puts(&del_spec, head->name));
+ cl_git_pass(git_vector_insert(delete_specs, git_buf_detach(&del_spec)));
+ }
+
+ return 0;
+}
+
+int record_ref_cb(git_remote_head *head, void *payload)
+{
+ git_vector *refs = (git_vector *) payload;
+ return git_vector_insert(refs, head);
+}
+
+void verify_remote_refs(git_vector *actual_refs, const expected_ref expected_refs[], size_t expected_refs_len)
+{
+ size_t i, j = 0;
+ git_buf msg = GIT_BUF_INIT;
+ git_remote_head *actual;
+ char *oid_str;
+ bool master_present = false;
+
+ /* We don't care whether "master" is present on the other end or not */
+ git_vector_foreach(actual_refs, i, actual) {
+ if (!strcmp(actual->name, "refs/heads/master")) {
+ master_present = true;
+ break;
+ }
+ }
+
+ if (expected_refs_len + (master_present ? 1 : 0) != actual_refs->length)
+ goto failed;
+
+ git_vector_foreach(actual_refs, i, actual) {
+ if (master_present && !strcmp(actual->name, "refs/heads/master"))
+ continue;
+
+ if (strcmp(expected_refs[j].name, actual->name) ||
+ git_oid_cmp(expected_refs[j].oid, &actual->oid))
+ goto failed;
+
+ j++;
+ }
+
+ return;
+
+failed:
+ git_buf_puts(&msg, "Expected and actual refs differ:\nEXPECTED:\n");
+
+ for(i = 0; i < expected_refs_len; i++) {
+ cl_assert(oid_str = git_oid_allocfmt(expected_refs[i].oid));
+ cl_git_pass(git_buf_printf(&msg, "%s = %s\n", expected_refs[i].name, oid_str));
+ git__free(oid_str);
+ }
+
+ git_buf_puts(&msg, "\nACTUAL:\n");
+ git_vector_foreach(actual_refs, i, actual) {
+ if (master_present && !strcmp(actual->name, "refs/heads/master"))
+ continue;
+
+ cl_assert(oid_str = git_oid_allocfmt(&actual->oid));
+ cl_git_pass(git_buf_printf(&msg, "%s = %s\n", actual->name, oid_str));
+ git__free(oid_str);
+ }
+
+ cl_fail(git_buf_cstr(&msg));
+
+ git_buf_free(&msg);
+}
diff --git a/tests-clar/network/push_util.h b/tests-clar/network/push_util.h
new file mode 100644
index 000000000..2f4dffce4
--- /dev/null
+++ b/tests-clar/network/push_util.h
@@ -0,0 +1,68 @@
+#ifndef INCLUDE_cl_push_util_h__
+#define INCLUDE_cl_push_util_h__
+
+#include "git2/oid.h"
+
+/* Constant for zero oid */
+extern const git_oid OID_ZERO;
+
+/**
+ * Macro for initializing git_remote_callbacks to use test helpers that
+ * record data in a record_callbacks_data instance.
+ * @param data pointer to a record_callbacks_data instance
+ */
+#define RECORD_CALLBACKS_INIT(data) { NULL, NULL, record_update_tips_cb, data }
+
+typedef struct {
+ char *name;
+ git_oid *old_oid;
+ git_oid *new_oid;
+} updated_tip;
+
+typedef struct {
+ git_vector updated_tips;
+} record_callbacks_data;
+
+typedef struct {
+ const char *name;
+ const git_oid *oid;
+} expected_ref;
+
+void updated_tip_free(updated_tip *t);
+
+void record_callbacks_data_clear(record_callbacks_data *data);
+
+/**
+ * Callback for git_remote_update_tips that records updates
+ *
+ * @param data (git_vector *) of updated_tip instances
+ */
+int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data);
+
+/**
+ * Callback for git_remote_list that adds refspecs to delete each ref
+ *
+ * @param head a ref on the remote
+ * @param payload a git_push instance
+ */
+int delete_ref_cb(git_remote_head *head, void *payload);
+
+/**
+ * Callback for git_remote_list that adds refspecs to vector
+ *
+ * @param head a ref on the remote
+ * @param payload (git_vector *) of git_remote_head instances
+ */
+int record_ref_cb(git_remote_head *head, void *payload);
+
+/**
+ * Verifies that refs on remote stored by record_ref_cb match the expected
+ * names, oids, and order.
+ *
+ * @param actual_refs actual refs stored by record_ref_cb()
+ * @param expected_refs expected remote refs
+ * @param expected_refs_len length of expected_refs
+ */
+void verify_remote_refs(git_vector *actual_refs, const expected_ref expected_refs[], size_t expected_refs_len);
+
+#endif /* INCLUDE_cl_push_util_h__ */
diff --git a/tests-clar/object/lookup.c b/tests-clar/object/lookup.c
index 01435bc04..47efe88cb 100644
--- a/tests-clar/object/lookup.c
+++ b/tests-clar/object/lookup.c
@@ -62,3 +62,15 @@ void test_object_lookup__lookup_wrong_type_eventually_returns_enotfound(void)
cl_assert_equal_i(
GIT_ENOTFOUND, git_object_lookup(&object, g_repo, &oid, GIT_OBJ_TAG));
}
+
+void test_object_lookup__lookup_object_type_by_oid(void)
+{
+ const char *commit = "e90810b8df3e80c413d903f631643c716887138d";
+ git_oid oid;
+ git_otype type;
+
+ cl_git_pass(git_oid_fromstr(&oid, commit));
+
+ cl_git_pass(git_object_oid2type(&type, g_repo, &oid));
+ cl_assert(type == GIT_OBJ_COMMIT);
+}
diff --git a/tests-clar/resources/push.sh b/tests-clar/resources/push.sh
new file mode 100644
index 000000000..607117675
--- /dev/null
+++ b/tests-clar/resources/push.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#creates push_src repo for libgit2 push tests.
+set -eu
+
+#Create src repo for push
+mkdir push_src
+pushd push_src
+ git init
+
+ echo a > a.txt
+ git add .
+ git commit -m 'added a.txt'
+
+ mkdir fold
+ echo b > fold/b.txt
+ git add .
+ git commit -m 'added fold and fold/b.txt'
+
+ git branch b1 #b1 and b2 are the same
+ git branch b2
+
+ git checkout -b b3
+ echo edit >> a.txt
+ git add .
+ git commit -m 'edited a.txt'
+
+ git checkout -b b4 master
+ echo edit >> fold\b.txt
+ git add .
+ git commit -m 'edited fold\b.txt'
+
+ git checkout -b b5 master
+ git submodule add ../testrepo.git submodule
+ git commit -m "added submodule named 'submodule' pointing to '../testrepo.git'"
+
+ git checkout master
+ git merge -m "merge b3, b4, and b5 to master" b3 b4 b5
+
+ #Log commits to include in testcase
+ git log --format=oneline --decorate --graph
+ #*-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, master) merge b3, b4, and b5 to master
+ #|\ \
+ #| | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git'
+ #| * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt
+ #| |/
+ #* | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt
+ #|/
+ #* a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt
+ #* 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt
+
+ #fix paths so that we can add repo folders under libgit2 repo
+ #rename .git to .gitted
+ find . -name .git -exec mv -i '{}' '{}ted' \;
+ mv -i .gitmodules gitmodules
+popd
diff --git a/tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG b/tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG
new file mode 100644
index 000000000..b1295084c
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/COMMIT_EDITMSG
@@ -0,0 +1 @@
+added submodule named 'submodule' pointing to '../testrepo.git'
diff --git a/tests-clar/resources/push_src/.gitted/HEAD b/tests-clar/resources/push_src/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/push_src/.gitted/ORIG_HEAD b/tests-clar/resources/push_src/.gitted/ORIG_HEAD
new file mode 100644
index 000000000..afadf9d26
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/ORIG_HEAD
@@ -0,0 +1 @@
+a78705c3b2725f931d3ee05348d83cc26700f247
diff --git a/tests-clar/resources/push_src/.gitted/config b/tests-clar/resources/push_src/.gitted/config
new file mode 100644
index 000000000..51de0311b
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/config
@@ -0,0 +1,10 @@
+[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ symlinks = false
+ ignorecase = true
+ hideDotFiles = dotGitOnly
+[submodule "submodule"]
+ url = m:/dd/libgit2/tests-clar/resources/testrepo.git
diff --git a/tests-clar/resources/push_src/.gitted/description b/tests-clar/resources/push_src/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests-clar/resources/push_src/.gitted/hooks/applypatch-msg.sample b/tests-clar/resources/push_src/.gitted/hooks/applypatch-msg.sample
new file mode 100644
index 000000000..8b2a2fe84
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/applypatch-msg.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit. The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "applypatch-msg".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/commit-msg" &&
+ exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:
diff --git a/tests-clar/resources/push_src/.gitted/hooks/commit-msg.sample b/tests-clar/resources/push_src/.gitted/hooks/commit-msg.sample
new file mode 100644
index 000000000..b58d1184a
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/commit-msg.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message. The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit. The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
+ echo >&2 Duplicate Signed-off-by lines.
+ exit 1
+}
diff --git a/tests-clar/resources/push_src/.gitted/hooks/post-commit.sample b/tests-clar/resources/push_src/.gitted/hooks/post-commit.sample
new file mode 100644
index 000000000..22668216a
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/post-commit.sample
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, rename this file to "post-commit".
+
+: Nothing
diff --git a/tests-clar/resources/push_src/.gitted/hooks/post-receive.sample b/tests-clar/resources/push_src/.gitted/hooks/post-receive.sample
new file mode 100644
index 000000000..7a83e17ab
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/post-receive.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script for the "post-receive" event.
+#
+# The "post-receive" script is run after receive-pack has accepted a pack
+# and the repository has been updated. It is passed arguments in through
+# stdin in the form
+# <oldrev> <newrev> <refname>
+# For example:
+# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
+#
+# see contrib/hooks/ for a sample, or uncomment the next line and
+# rename the file to "post-receive".
+
+#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
diff --git a/tests-clar/resources/push_src/.gitted/hooks/post-update.sample b/tests-clar/resources/push_src/.gitted/hooks/post-update.sample
new file mode 100644
index 000000000..ec17ec193
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/post-update.sample
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, rename this file to "post-update".
+
+exec git update-server-info
diff --git a/tests-clar/resources/push_src/.gitted/hooks/pre-applypatch.sample b/tests-clar/resources/push_src/.gitted/hooks/pre-applypatch.sample
new file mode 100644
index 000000000..b1f187c2e
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/pre-applypatch.sample
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-applypatch".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+ exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:
diff --git a/tests-clar/resources/push_src/.gitted/hooks/pre-commit.sample b/tests-clar/resources/push_src/.gitted/hooks/pre-commit.sample
new file mode 100644
index 000000000..18c482976
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/pre-commit.sample
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-commit".
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+# If you want to allow non-ascii filenames set this variable to true.
+allownonascii=$(git config hooks.allownonascii)
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Cross platform projects tend to avoid non-ascii filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+ # Note that the use of brackets around a tr range is ok here, (it's
+ # even required, for portability to Solaris 10's /usr/bin/tr), since
+ # the square bracket bytes happen to fall in the designated range.
+ test $(git diff --cached --name-only --diff-filter=A -z $against |
+ LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
+then
+ echo "Error: Attempt to add a non-ascii file name."
+ echo
+ echo "This can cause problems if you want to work"
+ echo "with people on other platforms."
+ echo
+ echo "To be portable it is advisable to rename the file ..."
+ echo
+ echo "If you know what you are doing you can disable this"
+ echo "check using:"
+ echo
+ echo " git config hooks.allownonascii true"
+ echo
+ exit 1
+fi
+
+# If there are whitespace errors, print the offending file names and fail.
+exec git diff-index --check --cached $against --
diff --git a/tests-clar/resources/push_src/.gitted/hooks/pre-rebase.sample b/tests-clar/resources/push_src/.gitted/hooks/pre-rebase.sample
new file mode 100644
index 000000000..9773ed4cb
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/pre-rebase.sample
@@ -0,0 +1,169 @@
+#!/bin/sh
+#
+# Copyright (c) 2006, 2008 Junio C Hamano
+#
+# The "pre-rebase" hook is run just before "git rebase" starts doing
+# its job, and can prevent the command from running by exiting with
+# non-zero status.
+#
+# The hook is called with the following parameters:
+#
+# $1 -- the upstream the series was forked from.
+# $2 -- the branch being rebased (or empty when rebasing the current branch).
+#
+# This sample shows how to prevent topic branches that are already
+# merged to 'next' branch from getting rebased, because allowing it
+# would result in rebasing already published history.
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+ topic="refs/heads/$2"
+else
+ topic=`git symbolic-ref HEAD` ||
+ exit 0 ;# we do not interrupt rebasing detached HEAD
+fi
+
+case "$topic" in
+refs/heads/??/*)
+ ;;
+*)
+ exit 0 ;# we do not interrupt others.
+ ;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master. Is it OK to rebase it?
+
+# Does the topic really exist?
+git show-ref -q "$topic" || {
+ echo >&2 "No such branch $topic"
+ exit 1
+}
+
+# Is topic fully merged to master?
+not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+ echo >&2 "$topic is fully merged to master; better remove it."
+ exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next? If so you should not be rebasing it.
+only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git rev-list ^master ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+ not_in_topic=`git rev-list "^$topic" master`
+ if test -z "$not_in_topic"
+ then
+ echo >&2 "$topic is already up-to-date with master"
+ exit 1 ;# we could allow it, but there is no point.
+ else
+ exit 0
+ fi
+else
+ not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
+ /usr/bin/perl -e '
+ my $topic = $ARGV[0];
+ my $msg = "* $topic has commits already merged to public branch:\n";
+ my (%not_in_next) = map {
+ /^([0-9a-f]+) /;
+ ($1 => 1);
+ } split(/\n/, $ARGV[1]);
+ for my $elem (map {
+ /^([0-9a-f]+) (.*)$/;
+ [$1 => $2];
+ } split(/\n/, $ARGV[2])) {
+ if (!exists $not_in_next{$elem->[0]}) {
+ if ($msg) {
+ print STDERR $msg;
+ undef $msg;
+ }
+ print STDERR " $elem->[1]\n";
+ }
+ }
+ ' "$topic" "$not_in_next" "$not_in_master"
+ exit 1
+fi
+
+exit 0
+
+################################################################
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+ merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+ it is deleted. If you need to build on top of it to correct
+ earlier mistakes, a new topic branch is created by forking at
+ the tip of the "master". This is not strictly necessary, but
+ it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+ branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next". Young
+ topic branches can have stupid mistakes you would rather
+ clean up before publishing, and things that have not been
+ merged into other branches can be easily rebased without
+ affecting other people. But once it is published, you would
+ not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+ Then you can delete it. More importantly, you should not
+ build on top of it -- other people may already want to
+ change things related to the topic as patches against your
+ "master", so if you need further changes, it is better to
+ fork the topic (perhaps with the same name) afresh from the
+ tip of "master".
+
+Let's look at this example:
+
+ o---o---o---o---o---o---o---o---o---o "next"
+ / / / /
+ / a---a---b A / /
+ / / / /
+ / / c---c---c---c B /
+ / / / \ /
+ / / / b---b C \ /
+ / / / / \ /
+ ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished. It has been fully merged up to "master" and "next",
+ and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+ git rev-list ^master ^topic next
+ git rev-list ^master next
+
+ if these match, topic has not merged in next at all.
+
+To compute (2):
+
+ git rev-list master..topic
+
+ if this is empty, it is fully merged to "master".
diff --git a/tests-clar/resources/push_src/.gitted/hooks/prepare-commit-msg.sample b/tests-clar/resources/push_src/.gitted/hooks/prepare-commit-msg.sample
new file mode 100644
index 000000000..f093a02ec
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/prepare-commit-msg.sample
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by "git commit" with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source. The hook's purpose is to edit the commit
+# message file. If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, rename this file to "prepare-commit-msg".
+
+# This hook includes three examples. The first comments out the
+# "Conflicts:" part of a merge commit.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output. It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited. This is rarely a good idea.
+
+case "$2,$3" in
+ merge,)
+ /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
+
+# ,|template,)
+# /usr/bin/perl -i.bak -pe '
+# print "\n" . `git diff --cached --name-status -r`
+# if /^#/ && $first++ == 0' "$1" ;;
+
+ *) ;;
+esac
+
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
diff --git a/tests-clar/resources/push_src/.gitted/hooks/update.sample b/tests-clar/resources/push_src/.gitted/hooks/update.sample
new file mode 100644
index 000000000..71ab04edc
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/hooks/update.sample
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# An example hook script to blocks unannotated tags from entering.
+# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
+#
+# To enable this hook, rename this file to "update".
+#
+# Config
+# ------
+# hooks.allowunannotated
+# This boolean sets whether unannotated tags will be allowed into the
+# repository. By default they won't be.
+# hooks.allowdeletetag
+# This boolean sets whether deleting tags will be allowed in the
+# repository. By default they won't be.
+# hooks.allowmodifytag
+# This boolean sets whether a tag may be modified after creation. By default
+# it won't be.
+# hooks.allowdeletebranch
+# This boolean sets whether deleting branches will be allowed in the
+# repository. By default they won't be.
+# hooks.denycreatebranch
+# This boolean sets whether remotely creating branches will be denied
+# in the repository. By default this is allowed.
+#
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+ echo "Don't run this script from the command line." >&2
+ echo " (if you want, you could supply GIT_DIR then run" >&2
+ echo " $0 <ref> <oldrev> <newrev>)" >&2
+ exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+ echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+ exit 1
+fi
+
+# --- Config
+allowunannotated=$(git config --bool hooks.allowunannotated)
+allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --bool hooks.denycreatebranch)
+allowdeletetag=$(git config --bool hooks.allowdeletetag)
+allowmodifytag=$(git config --bool hooks.allowmodifytag)
+
+# check for no description
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+case "$projectdesc" in
+"Unnamed repository"* | "")
+ echo "*** Project description file hasn't been set" >&2
+ exit 1
+ ;;
+esac
+
+# --- Check types
+# if $newrev is 0000...0000, it's a commit to delete a ref.
+zero="0000000000000000000000000000000000000000"
+if [ "$newrev" = "$zero" ]; then
+ newrev_type=delete
+else
+ newrev_type=$(git cat-file -t $newrev)
+fi
+
+case "$refname","$newrev_type" in
+ refs/tags/*,commit)
+ # un-annotated tag
+ short_refname=${refname##refs/tags/}
+ if [ "$allowunannotated" != "true" ]; then
+ echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
+ echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,delete)
+ # delete tag
+ if [ "$allowdeletetag" != "true" ]; then
+ echo "*** Deleting a tag is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,tag)
+ # annotated tag
+ if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
+ then
+ echo "*** Tag '$refname' already exists." >&2
+ echo "*** Modifying a tag is not allowed in this repository." >&2
+ exit 1
+ fi
+ ;;
+ refs/heads/*,commit)
+ # branch
+ if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+ echo "*** Creating a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/heads/*,delete)
+ # delete branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/remotes/*,commit)
+ # tracking branch
+ ;;
+ refs/remotes/*,delete)
+ # delete tracking branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ *)
+ # Anything else (is there anything else?)
+ echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
+ exit 1
+ ;;
+esac
+
+# --- Finished
+exit 0
diff --git a/tests-clar/resources/push_src/.gitted/index b/tests-clar/resources/push_src/.gitted/index
new file mode 100644
index 000000000..0ef6594b3
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/index
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/info/exclude b/tests-clar/resources/push_src/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests-clar/resources/push_src/.gitted/logs/HEAD b/tests-clar/resources/push_src/.gitted/logs/HEAD
new file mode 100644
index 000000000..4ef336f84
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/logs/HEAD
@@ -0,0 +1,10 @@
+0000000000000000000000000000000000000000 5c0bb3d1b9449d1cc69d7519fd05166f01840915 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit (initial): added a.txt
+5c0bb3d1b9449d1cc69d7519fd05166f01840915 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit: added fold and fold/b.txt
+a78705c3b2725f931d3ee05348d83cc26700f247 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from master to b3
+a78705c3b2725f931d3ee05348d83cc26700f247 d9b63a88223d8367516f50bd131a5f7349b7f3e4 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited a.txt
+d9b63a88223d8367516f50bd131a5f7349b7f3e4 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from b3 to b4
+a78705c3b2725f931d3ee05348d83cc26700f247 27b7ce66243eb1403862d05f958c002312df173d Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited fold\b.txt
+27b7ce66243eb1403862d05f958c002312df173d a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 checkout: moving from b4 to b5
+a78705c3b2725f931d3ee05348d83cc26700f247 fa38b91f199934685819bea316186d8b008c52a2 Congyi Wu <congyiwu@gmail.com> 1352923206 -0500 commit: added submodule named 'submodule' pointing to '../testrepo.git'
+fa38b91f199934685819bea316186d8b008c52a2 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 checkout: moving from b5 to master
+a78705c3b2725f931d3ee05348d83cc26700f247 951bbbb90e2259a4c8950db78946784fb53fcbce Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 merge b3 b4 b5: Merge made by the 'octopus' strategy.
diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b1 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b1
new file mode 100644
index 000000000..390a03d5c
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b1
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 branch: Created from master
diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b2 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b2
new file mode 100644
index 000000000..390a03d5c
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b2
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 branch: Created from master
diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b3 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b3
new file mode 100644
index 000000000..01e302c44
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b3
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from HEAD
+a78705c3b2725f931d3ee05348d83cc26700f247 d9b63a88223d8367516f50bd131a5f7349b7f3e4 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited a.txt
diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b4 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b4
new file mode 100644
index 000000000..7afddc54e
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b4
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from master
+a78705c3b2725f931d3ee05348d83cc26700f247 27b7ce66243eb1403862d05f958c002312df173d Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 commit: edited fold\b.txt
diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/b5 b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b5
new file mode 100644
index 000000000..bc22567f7
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/b5
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923201 -0500 branch: Created from master
+a78705c3b2725f931d3ee05348d83cc26700f247 fa38b91f199934685819bea316186d8b008c52a2 Congyi Wu <congyiwu@gmail.com> 1352923206 -0500 commit: added submodule named 'submodule' pointing to '../testrepo.git'
diff --git a/tests-clar/resources/push_src/.gitted/logs/refs/heads/master b/tests-clar/resources/push_src/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..8aafa9ca4
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/logs/refs/heads/master
@@ -0,0 +1,3 @@
+0000000000000000000000000000000000000000 5c0bb3d1b9449d1cc69d7519fd05166f01840915 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit (initial): added a.txt
+5c0bb3d1b9449d1cc69d7519fd05166f01840915 a78705c3b2725f931d3ee05348d83cc26700f247 Congyi Wu <congyiwu@gmail.com> 1352923200 -0500 commit: added fold and fold/b.txt
+a78705c3b2725f931d3ee05348d83cc26700f247 951bbbb90e2259a4c8950db78946784fb53fcbce Congyi Wu <congyiwu@gmail.com> 1352923207 -0500 merge b3 b4 b5: Merge made by the 'octopus' strategy.
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/HEAD b/tests-clar/resources/push_src/.gitted/modules/submodule/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/config b/tests-clar/resources/push_src/.gitted/modules/submodule/config
new file mode 100644
index 000000000..59810077d
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/config
@@ -0,0 +1,15 @@
+[core]
+ repositoryformatversion = 0
+ filemode = false
+ bare = false
+ logallrefupdates = true
+ worktree = ../../../submodule
+ symlinks = false
+ ignorecase = true
+ hideDotFiles = dotGitOnly
+[remote "origin"]
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ url = m:/dd/libgit2/tests-clar/resources/testrepo.git
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/description b/tests-clar/resources/push_src/.gitted/modules/submodule/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/applypatch-msg.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/applypatch-msg.sample
new file mode 100644
index 000000000..8b2a2fe84
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/applypatch-msg.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message taken by
+# applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit. The hook is
+# allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "applypatch-msg".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/commit-msg" &&
+ exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}
+:
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/commit-msg.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/commit-msg.sample
new file mode 100644
index 000000000..b58d1184a
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/commit-msg.sample
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message. The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit. The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+ sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
+ echo >&2 Duplicate Signed-off-by lines.
+ exit 1
+}
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-commit.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-commit.sample
new file mode 100644
index 000000000..22668216a
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-commit.sample
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script that is called after a successful
+# commit is made.
+#
+# To enable this hook, rename this file to "post-commit".
+
+: Nothing
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-receive.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-receive.sample
new file mode 100644
index 000000000..7a83e17ab
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-receive.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# An example hook script for the "post-receive" event.
+#
+# The "post-receive" script is run after receive-pack has accepted a pack
+# and the repository has been updated. It is passed arguments in through
+# stdin in the form
+# <oldrev> <newrev> <refname>
+# For example:
+# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master
+#
+# see contrib/hooks/ for a sample, or uncomment the next line and
+# rename the file to "post-receive".
+
+#. /usr/share/doc/git-core/contrib/hooks/post-receive-email
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-update.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-update.sample
new file mode 100644
index 000000000..ec17ec193
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/post-update.sample
@@ -0,0 +1,8 @@
+#!/bin/sh
+#
+# An example hook script to prepare a packed repository for use over
+# dumb transports.
+#
+# To enable this hook, rename this file to "post-update".
+
+exec git update-server-info
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-applypatch.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-applypatch.sample
new file mode 100644
index 000000000..b1f187c2e
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-applypatch.sample
@@ -0,0 +1,14 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed
+# by applypatch from an e-mail message.
+#
+# The hook should exit with non-zero status after issuing an
+# appropriate message if it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-applypatch".
+
+. git-sh-setup
+test -x "$GIT_DIR/hooks/pre-commit" &&
+ exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
+:
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-commit.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-commit.sample
new file mode 100644
index 000000000..18c482976
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-commit.sample
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# An example hook script to verify what is about to be committed.
+# Called by "git commit" with no arguments. The hook should
+# exit with non-zero status after issuing an appropriate message if
+# it wants to stop the commit.
+#
+# To enable this hook, rename this file to "pre-commit".
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+# If you want to allow non-ascii filenames set this variable to true.
+allownonascii=$(git config hooks.allownonascii)
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Cross platform projects tend to avoid non-ascii filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+ # Note that the use of brackets around a tr range is ok here, (it's
+ # even required, for portability to Solaris 10's /usr/bin/tr), since
+ # the square bracket bytes happen to fall in the designated range.
+ test $(git diff --cached --name-only --diff-filter=A -z $against |
+ LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
+then
+ echo "Error: Attempt to add a non-ascii file name."
+ echo
+ echo "This can cause problems if you want to work"
+ echo "with people on other platforms."
+ echo
+ echo "To be portable it is advisable to rename the file ..."
+ echo
+ echo "If you know what you are doing you can disable this"
+ echo "check using:"
+ echo
+ echo " git config hooks.allownonascii true"
+ echo
+ exit 1
+fi
+
+# If there are whitespace errors, print the offending file names and fail.
+exec git diff-index --check --cached $against --
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-rebase.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-rebase.sample
new file mode 100644
index 000000000..9773ed4cb
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/pre-rebase.sample
@@ -0,0 +1,169 @@
+#!/bin/sh
+#
+# Copyright (c) 2006, 2008 Junio C Hamano
+#
+# The "pre-rebase" hook is run just before "git rebase" starts doing
+# its job, and can prevent the command from running by exiting with
+# non-zero status.
+#
+# The hook is called with the following parameters:
+#
+# $1 -- the upstream the series was forked from.
+# $2 -- the branch being rebased (or empty when rebasing the current branch).
+#
+# This sample shows how to prevent topic branches that are already
+# merged to 'next' branch from getting rebased, because allowing it
+# would result in rebasing already published history.
+
+publish=next
+basebranch="$1"
+if test "$#" = 2
+then
+ topic="refs/heads/$2"
+else
+ topic=`git symbolic-ref HEAD` ||
+ exit 0 ;# we do not interrupt rebasing detached HEAD
+fi
+
+case "$topic" in
+refs/heads/??/*)
+ ;;
+*)
+ exit 0 ;# we do not interrupt others.
+ ;;
+esac
+
+# Now we are dealing with a topic branch being rebased
+# on top of master. Is it OK to rebase it?
+
+# Does the topic really exist?
+git show-ref -q "$topic" || {
+ echo >&2 "No such branch $topic"
+ exit 1
+}
+
+# Is topic fully merged to master?
+not_in_master=`git rev-list --pretty=oneline ^master "$topic"`
+if test -z "$not_in_master"
+then
+ echo >&2 "$topic is fully merged to master; better remove it."
+ exit 1 ;# we could allow it, but there is no point.
+fi
+
+# Is topic ever merged to next? If so you should not be rebasing it.
+only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`
+only_next_2=`git rev-list ^master ${publish} | sort`
+if test "$only_next_1" = "$only_next_2"
+then
+ not_in_topic=`git rev-list "^$topic" master`
+ if test -z "$not_in_topic"
+ then
+ echo >&2 "$topic is already up-to-date with master"
+ exit 1 ;# we could allow it, but there is no point.
+ else
+ exit 0
+ fi
+else
+ not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`
+ /usr/bin/perl -e '
+ my $topic = $ARGV[0];
+ my $msg = "* $topic has commits already merged to public branch:\n";
+ my (%not_in_next) = map {
+ /^([0-9a-f]+) /;
+ ($1 => 1);
+ } split(/\n/, $ARGV[1]);
+ for my $elem (map {
+ /^([0-9a-f]+) (.*)$/;
+ [$1 => $2];
+ } split(/\n/, $ARGV[2])) {
+ if (!exists $not_in_next{$elem->[0]}) {
+ if ($msg) {
+ print STDERR $msg;
+ undef $msg;
+ }
+ print STDERR " $elem->[1]\n";
+ }
+ }
+ ' "$topic" "$not_in_next" "$not_in_master"
+ exit 1
+fi
+
+exit 0
+
+################################################################
+
+This sample hook safeguards topic branches that have been
+published from being rewound.
+
+The workflow assumed here is:
+
+ * Once a topic branch forks from "master", "master" is never
+ merged into it again (either directly or indirectly).
+
+ * Once a topic branch is fully cooked and merged into "master",
+ it is deleted. If you need to build on top of it to correct
+ earlier mistakes, a new topic branch is created by forking at
+ the tip of the "master". This is not strictly necessary, but
+ it makes it easier to keep your history simple.
+
+ * Whenever you need to test or publish your changes to topic
+ branches, merge them into "next" branch.
+
+The script, being an example, hardcodes the publish branch name
+to be "next", but it is trivial to make it configurable via
+$GIT_DIR/config mechanism.
+
+With this workflow, you would want to know:
+
+(1) ... if a topic branch has ever been merged to "next". Young
+ topic branches can have stupid mistakes you would rather
+ clean up before publishing, and things that have not been
+ merged into other branches can be easily rebased without
+ affecting other people. But once it is published, you would
+ not want to rewind it.
+
+(2) ... if a topic branch has been fully merged to "master".
+ Then you can delete it. More importantly, you should not
+ build on top of it -- other people may already want to
+ change things related to the topic as patches against your
+ "master", so if you need further changes, it is better to
+ fork the topic (perhaps with the same name) afresh from the
+ tip of "master".
+
+Let's look at this example:
+
+ o---o---o---o---o---o---o---o---o---o "next"
+ / / / /
+ / a---a---b A / /
+ / / / /
+ / / c---c---c---c B /
+ / / / \ /
+ / / / b---b C \ /
+ / / / / \ /
+ ---o---o---o---o---o---o---o---o---o---o---o "master"
+
+
+A, B and C are topic branches.
+
+ * A has one fix since it was merged up to "next".
+
+ * B has finished. It has been fully merged up to "master" and "next",
+ and is ready to be deleted.
+
+ * C has not merged to "next" at all.
+
+We would want to allow C to be rebased, refuse A, and encourage
+B to be deleted.
+
+To compute (1):
+
+ git rev-list ^master ^topic next
+ git rev-list ^master next
+
+ if these match, topic has not merged in next at all.
+
+To compute (2):
+
+ git rev-list master..topic
+
+ if this is empty, it is fully merged to "master".
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/prepare-commit-msg.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/prepare-commit-msg.sample
new file mode 100644
index 000000000..f093a02ec
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/prepare-commit-msg.sample
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# An example hook script to prepare the commit log message.
+# Called by "git commit" with the name of the file that has the
+# commit message, followed by the description of the commit
+# message's source. The hook's purpose is to edit the commit
+# message file. If the hook fails with a non-zero status,
+# the commit is aborted.
+#
+# To enable this hook, rename this file to "prepare-commit-msg".
+
+# This hook includes three examples. The first comments out the
+# "Conflicts:" part of a merge commit.
+#
+# The second includes the output of "git diff --name-status -r"
+# into the message, just before the "git status" output. It is
+# commented because it doesn't cope with --amend or with squashed
+# commits.
+#
+# The third example adds a Signed-off-by line to the message, that can
+# still be edited. This is rarely a good idea.
+
+case "$2,$3" in
+ merge,)
+ /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
+
+# ,|template,)
+# /usr/bin/perl -i.bak -pe '
+# print "\n" . `git diff --cached --name-status -r`
+# if /^#/ && $first++ == 0' "$1" ;;
+
+ *) ;;
+esac
+
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/update.sample b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/update.sample
new file mode 100644
index 000000000..71ab04edc
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/hooks/update.sample
@@ -0,0 +1,128 @@
+#!/bin/sh
+#
+# An example hook script to blocks unannotated tags from entering.
+# Called by "git receive-pack" with arguments: refname sha1-old sha1-new
+#
+# To enable this hook, rename this file to "update".
+#
+# Config
+# ------
+# hooks.allowunannotated
+# This boolean sets whether unannotated tags will be allowed into the
+# repository. By default they won't be.
+# hooks.allowdeletetag
+# This boolean sets whether deleting tags will be allowed in the
+# repository. By default they won't be.
+# hooks.allowmodifytag
+# This boolean sets whether a tag may be modified after creation. By default
+# it won't be.
+# hooks.allowdeletebranch
+# This boolean sets whether deleting branches will be allowed in the
+# repository. By default they won't be.
+# hooks.denycreatebranch
+# This boolean sets whether remotely creating branches will be denied
+# in the repository. By default this is allowed.
+#
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+ echo "Don't run this script from the command line." >&2
+ echo " (if you want, you could supply GIT_DIR then run" >&2
+ echo " $0 <ref> <oldrev> <newrev>)" >&2
+ exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+ echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+ exit 1
+fi
+
+# --- Config
+allowunannotated=$(git config --bool hooks.allowunannotated)
+allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --bool hooks.denycreatebranch)
+allowdeletetag=$(git config --bool hooks.allowdeletetag)
+allowmodifytag=$(git config --bool hooks.allowmodifytag)
+
+# check for no description
+projectdesc=$(sed -e '1q' "$GIT_DIR/description")
+case "$projectdesc" in
+"Unnamed repository"* | "")
+ echo "*** Project description file hasn't been set" >&2
+ exit 1
+ ;;
+esac
+
+# --- Check types
+# if $newrev is 0000...0000, it's a commit to delete a ref.
+zero="0000000000000000000000000000000000000000"
+if [ "$newrev" = "$zero" ]; then
+ newrev_type=delete
+else
+ newrev_type=$(git cat-file -t $newrev)
+fi
+
+case "$refname","$newrev_type" in
+ refs/tags/*,commit)
+ # un-annotated tag
+ short_refname=${refname##refs/tags/}
+ if [ "$allowunannotated" != "true" ]; then
+ echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2
+ echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,delete)
+ # delete tag
+ if [ "$allowdeletetag" != "true" ]; then
+ echo "*** Deleting a tag is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/tags/*,tag)
+ # annotated tag
+ if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1
+ then
+ echo "*** Tag '$refname' already exists." >&2
+ echo "*** Modifying a tag is not allowed in this repository." >&2
+ exit 1
+ fi
+ ;;
+ refs/heads/*,commit)
+ # branch
+ if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+ echo "*** Creating a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/heads/*,delete)
+ # delete branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ refs/remotes/*,commit)
+ # tracking branch
+ ;;
+ refs/remotes/*,delete)
+ # delete tracking branch
+ if [ "$allowdeletebranch" != "true" ]; then
+ echo "*** Deleting a tracking branch is not allowed in this repository" >&2
+ exit 1
+ fi
+ ;;
+ *)
+ # Anything else (is there anything else?)
+ echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2
+ exit 1
+ ;;
+esac
+
+# --- Finished
+exit 0
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/index b/tests-clar/resources/push_src/.gitted/modules/submodule/index
new file mode 100644
index 000000000..8e44080f3
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/index
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude b/tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/info/exclude
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD
new file mode 100644
index 000000000..aedcdf295
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master
new file mode 100644
index 000000000..aedcdf295
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/heads/master
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..aedcdf295
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/logs/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Congyi Wu <congyiwu@gmail.com> 1352923205 -0500 clone: from m:/dd/libgit2/tests-clar/resources/testrepo.git
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125
new file mode 100644
index 000000000..d1c032fce
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/08/b041783f40edfe12bb406c9c9a8a040177c125
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
new file mode 100644
index 000000000..cedb2a22e
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
new file mode 100644
index 000000000..93a16f146
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd
new file mode 100644
index 000000000..ba0bfb30c
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/18/10dff58d8a660512d4832e740f692884338ccd
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd
new file mode 100644
index 000000000..3ec541288
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1a/443023183e3f2bfbef8ac923cd81c1018a18fd
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689
new file mode 100644
index 000000000..6048d4bad
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1b/8cbad43e867676df601306689fe7c3def5e689
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
new file mode 100644
index 000000000..225c45734
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/1f/67fc4386b2d171e0d21be1c447e12660561f9b
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10
new file mode 100644
index 000000000..cb1ed5712
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
new file mode 100644
index 000000000..df40d99af
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/27/0b8ea76056d5cad83af921837702d3e3c2924d
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7
new file mode 100644
index 000000000..0a1500a6f
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/2d/59075e0681f540482d4f6223a68e0fef790bc7
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
new file mode 100644
index 000000000..321eaa867
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/32/59a6bd5b57fb9c1281bb7ed3167b50f224cb54
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
new file mode 100644
index 000000000..9bb5b623b
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/36/97d64be941a53d4ae8f6a271e4e3fa56b022cc
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
new file mode 100644
index 000000000..7ca4ceed5
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
new file mode 100644
index 000000000..8953b6cef
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
@@ -0,0 +1,2 @@
+xQ
+0D)6ͦ "xO-FbEo0 Ǥ,ske[Pn8R,EpD?g}^3 <GhYK8ЖDA);gݧjp4-r;sGA4ۺ=(in7IKFE \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea
new file mode 100644
index 000000000..b4e5aa186
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/4b/22b35d44b5a4f589edf3dc89196399771796ea
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91
new file mode 100644
index 000000000..351cff823
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/52/1d87c1ec3aef9824daf6d96cc0ae3710766d91
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
new file mode 100644
index 000000000..c1f22c54f
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
@@ -0,0 +1,2 @@
+x 1ENi@k2 "X$YW0YcÅszMD08!s Xgd::@X0Pw"F/RUzmZZV}|/o5I!1z:vUim}/>
+F- \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
new file mode 100644
index 000000000..2ef4faa0f
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
new file mode 100644
index 000000000..716b0c64b
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/76/3d71aadf09a7951596c9746c024e7eece7c7af
@@ -0,0 +1 @@
+xAj!?009o}H6}jUPPZ&Y AԛpFdpz[fYPqLJ.,Z`Ů.`v q $5+9Ot>/DE/龡W*eVdf1>覭ěʙFThk.i^0?PR, \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980
new file mode 100644
index 000000000..23c462f34
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/7b/4384978d2493e851f9cca7858815fac9b10980
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
new file mode 100644
index 000000000..2f9b6b6e3
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479
new file mode 100644
index 000000000..5df58dda5
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/96071c1b46c854b31185ea97743be6a8774479
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe
new file mode 100644
index 000000000..71019a636
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/84/9a5e34a26815e821f865b8479f5815a47af0fe
@@ -0,0 +1,2 @@
+xM F]s41x(IKݽ/_P@!8)es
+ N&FGSƄh{+CZzvF7Z-kx\[P8GK/^ l>.4 \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
new file mode 100644
index 000000000..4cc3f4dff
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/94/4c0f6e4dfa41595e6eb3ceecdb14f50fe18162
@@ -0,0 +1 @@
+x+)JMU044b040031QrutueXlmmAṃJ}G;UTWRQ`6Kǥ^/-*|W3Py`%E\&g|0{Ӎ1X \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
new file mode 100644
index 000000000..bf7b2bb68
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9a/03079b8a8ee85a0bee58bf9be3da8b62414ed4
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2
new file mode 100644
index 000000000..7f1cfb23c
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/13f7d0a9402c681f91dc590cf7b5470e6a77d2
@@ -0,0 +1,2 @@
+xM
+0F]d2<A~&`Vmҡ7U$JL9yM!GuH&U>;XEȎ5R AE &n}Z<E}=O[Ӽ^,^#ɿ]P`>A \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
new file mode 100644
index 000000000..a79612435
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
@@ -0,0 +1,3 @@
+x[
+0E*fդ "W0-Ft݁pS[Yx^
+Db CLhut}8X*4ZsYUA X3RM) s6輢Mរ&Jm;}<\@ޏpĀv?jۺL?H \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
new file mode 100644
index 000000000..f8588696b
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
@@ -0,0 +1,2 @@
+x;j1Dmdǎ|M3`V{ >QvL0I?!4Z=!צ8F!rsQy9]$D&l6A>jFWҵ IKNiZ%S
+ U~̽>' w [ DGڡQ-M>dO}\8g_ШoYr \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
new file mode 100644
index 000000000..29c8e824d
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a6/5fedf39aefe402d3bb6e24df4d4f5fe4547750
@@ -0,0 +1,3 @@
+xQ
+!@sBQ" ٱ r{<xƪ
+HlJSer!ZPTe*jUEo^2(XS€EDO<Yj$2s_&} ,}[~p7~<: Zp?1_C0 \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
new file mode 100644
index 000000000..d0d7e736e
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
new file mode 100644
index 000000000..18a7f61c2
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
new file mode 100644
index 000000000..d95254674
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/ae/90f12eea699729ed24555e40b9fd669da12a12
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1
new file mode 100644
index 000000000..f460f2547
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1
@@ -0,0 +1,2 @@
+xA
+0a9I p'1Ѷv\x{cVpvWgǎ0x[ ]"g#{rD Cot N U $?9-p+1^Qx9O\C m'D {mV(+l, \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
new file mode 100644
index 000000000..f613670e2
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/b6/361fc6a97178d8fc8639fdeed71c775ab52593
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
new file mode 100644
index 000000000..0817229bc
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644
@@ -0,0 +1,3 @@
+xKj1D)zUB-0uV9<#+W<J&8/seȕKJS
+Rv{QrYQN$H\E=6X5K Fr)(dCΆjs}9c-w8o\rI:
+l}FW$DsǣٚOWe]V8-Ý"U \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
new file mode 100644
index 000000000..75f541f10
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
@@ -0,0 +1,3 @@
+xQ
+0D)ʦI<'lR+FjEo0<xha ]șXUlPF)z4y,\r 'S-mI4
+Xh&F}n+\Y-p|鷜oUz;-alt{?I,:oRcHK \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659
new file mode 100644
index 000000000..f3b46b3ca
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d0/7b0f9a8c89f1d9e74dc4fce6421dec5ef8a659
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
new file mode 100644
index 000000000..a67d6e647
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d6/c93164c249c8000205dd4ec5cbca1b516d487f
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759
new file mode 100644
index 000000000..2d47e6faf
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/d7/1aab4f9b04b45ce09bcaa636a9be6231474759
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
new file mode 100644
index 000000000..b135eccda
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/e7/b4ad382349ff96dd8199000580b9b1e2042eb0
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
new file mode 100644
index 000000000..82e2790e8
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/f1/425cef211cc08caa31e7b545ffb232acb098c3
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
new file mode 100644
index 000000000..697c94c92
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92
new file mode 100644
index 000000000..112998d42
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fa/49b077972391ad58037050f2a75f74e3671e92
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765
new file mode 100644
index 000000000..12bf5f3e3
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/093bff70906175335656e6ce6ae05783708765
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09 b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09
new file mode 100644
index 000000000..158aef21f
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/fd/4959ce7510db09d4d8217fa2d1780413e05a09
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx
new file mode 100644
index 000000000..5068f2818
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack
new file mode 100644
index 000000000..a6a1f3020
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
new file mode 100644
index 000000000..94c3c71da
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
new file mode 100644
index 000000000..74c7fe4f3
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx
new file mode 100644
index 000000000..555cfa977
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack
new file mode 100644
index 000000000..4d539ed0a
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/packed-refs b/tests-clar/resources/push_src/.gitted/modules/submodule/packed-refs
new file mode 100644
index 000000000..506a8607c
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/packed-refs
@@ -0,0 +1,24 @@
+# pack-refs with: peeled
+a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/br2
+a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/cannot-fetch
+e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/chomped
+258f0e2a959a364e40ed6603d5d44fbb24765b10 refs/remotes/origin/haacked
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/remotes/origin/master
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/remotes/origin/not-good
+41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9 refs/remotes/origin/packed
+4a202b346bb0fb0db7eff3cffeb3c70babbd2045 refs/remotes/origin/packed-test
+763d71aadf09a7951596c9746c024e7eece7c7af refs/remotes/origin/subtrees
+e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/test
+9fd738e8f7967c078dceed8190330fc8648ee56a refs/remotes/origin/track-local
+e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/trailing
+521d87c1ec3aef9824daf6d96cc0ae3710766d91 refs/tags/annotated_tag_to_blob
+^1385f264afb75a56a5bec74243be9b367ba4ca08
+7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b
+^e90810b8df3e80c413d903f631643c716887138d
+849a5e34a26815e821f865b8479f5815a47af0fe refs/tags/hard_tag
+^a65fedf39aefe402d3bb6e24df4d4f5fe4547750
+1385f264afb75a56a5bec74243be9b367ba4ca08 refs/tags/point_to_blob
+b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/test
+^e90810b8df3e80c413d903f631643c716887138d
+849a5e34a26815e821f865b8479f5815a47af0fe refs/tags/wrapped_tag
+^a65fedf39aefe402d3bb6e24df4d4f5fe4547750
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/refs/heads/master b/tests-clar/resources/push_src/.gitted/modules/submodule/refs/heads/master
new file mode 100644
index 000000000..3d8f0a402
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/refs/heads/master
@@ -0,0 +1 @@
+a65fedf39aefe402d3bb6e24df4d4f5fe4547750
diff --git a/tests-clar/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD b/tests-clar/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD
new file mode 100644
index 000000000..6efe28fff
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/modules/submodule/refs/remotes/origin/HEAD
@@ -0,0 +1 @@
+ref: refs/remotes/origin/master
diff --git a/tests-clar/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca b/tests-clar/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca
new file mode 100644
index 000000000..39d126b2b
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/08/585692ce06452da6f82ae66b90d98b55536fca
@@ -0,0 +1 @@
+x+)JMU06f040031QH+(a!h;AE3Z* \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d b/tests-clar/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d
new file mode 100644
index 000000000..01d63b5c4
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/27/b7ce66243eb1403862d05f958c002312df173d
@@ -0,0 +1,4 @@
+xM
+0F]sti<7тmxwKe
+D."mr1@9>RȞyE
+ mH&Er7S!*u΄2>#\V8|Gt-ybhFU/J-|M}W+GK \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840 b/tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840
new file mode 100644
index 000000000..dc10f6831
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/28/905c54ea45a4bed8d7b90f51bd8bd81eec8840
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60 b/tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60
new file mode 100644
index 000000000..45c4d9208
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/36/6226fb970ac0caa9d3f55967ab01334a548f60
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915 b/tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915
new file mode 100644
index 000000000..883182138
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/5c/0bb3d1b9449d1cc69d7519fd05166f01840915
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472 b/tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472
new file mode 100644
index 000000000..586bf17a4
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/61/780798228d17af2d34fce4cfbdf35556832472
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d b/tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d
new file mode 100644
index 000000000..bcaaa91c2
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/64/fd55f9b6390202db5e5666fd1fb339089fba4d
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85 b/tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85
new file mode 100644
index 000000000..e814d07b0
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/78/981922613b2afb6025042ff6bd878ac1994e85
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce b/tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce
new file mode 100644
index 000000000..596cd43de
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/95/1bbbb90e2259a4c8950db78946784fb53fcbce
@@ -0,0 +1,2 @@
+x[J0}*f2$Eim
+\8:DfZ &65^,%lm١~;KJRXT蕄(!{¯DZqPqsPӈB\;EEgs`IeoE(YMFC9uu>|#?i.׻qD90FEENzܶ<\,\a_a.gd \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247 b/tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247
new file mode 100644
index 000000000..6ad835e86
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/a7/8705c3b2725f931d3ee05348d83cc26700f247
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd b/tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd
new file mode 100644
index 000000000..4e650aaa1
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/b4/e1f2b375a64c1ccd40c5ff6aa8bc96839ba4fd
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279 b/tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279
new file mode 100644
index 000000000..fcb2b32f3
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/c1/0409136a7a75e025fa502a1b2fd7b62b77d279
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c b/tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c
new file mode 100644
index 000000000..ad4272638
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/cd/881f90f2933db2e4cc26b8c71fe6037ac7fe4c
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4 b/tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4
new file mode 100644
index 000000000..b471e2155
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/d9/b63a88223d8367516f50bd131a5f7349b7f3e4
@@ -0,0 +1,2 @@
+xA
+0E]sd$ "xi2ՂmzwSErgj ![͎%wbY(zC/G\tw({r$C`Y[=DCu&V8!s=]8ޛT#|;ltWhfM}UQDM \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591 b/tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591
new file mode 100644
index 000000000..9f6b1502f
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/dc/ab83249f6f9d1ed735d651352a80519339b591
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928 b/tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928
new file mode 100644
index 000000000..b9813576d
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/f7/8a3106c85fb549c65198b2a2086276c6174928
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a b/tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a
new file mode 100644
index 000000000..888354fc2
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/f8/f7aefc2900a3d737cea9eee45729fd55761e1a
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2 b/tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2
new file mode 100644
index 000000000..13d9bca20
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/fa/38b91f199934685819bea316186d8b008c52a2
@@ -0,0 +1,2 @@
+xJ15>urrneo {f[)_DmIi7
+7Ĩ8ꔌ.n܃W)_T;x,(li[D\K墓XΓP?>W~|_Wؤxs6IcJNP}~ -מ/󄳭G X \ No newline at end of file
diff --git a/tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925 b/tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925
new file mode 100644
index 000000000..1cdc048c0
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/ff/fe95c7fd0a37fa2ed702f8f93b56b2196b3925
Binary files differ
diff --git a/tests-clar/resources/push_src/.gitted/objects/pack/dummy b/tests-clar/resources/push_src/.gitted/objects/pack/dummy
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/objects/pack/dummy
diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b1 b/tests-clar/resources/push_src/.gitted/refs/heads/b1
new file mode 100644
index 000000000..afadf9d26
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/refs/heads/b1
@@ -0,0 +1 @@
+a78705c3b2725f931d3ee05348d83cc26700f247
diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b2 b/tests-clar/resources/push_src/.gitted/refs/heads/b2
new file mode 100644
index 000000000..afadf9d26
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/refs/heads/b2
@@ -0,0 +1 @@
+a78705c3b2725f931d3ee05348d83cc26700f247
diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b3 b/tests-clar/resources/push_src/.gitted/refs/heads/b3
new file mode 100644
index 000000000..3056bb436
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/refs/heads/b3
@@ -0,0 +1 @@
+d9b63a88223d8367516f50bd131a5f7349b7f3e4
diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b4 b/tests-clar/resources/push_src/.gitted/refs/heads/b4
new file mode 100644
index 000000000..efed6f064
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/refs/heads/b4
@@ -0,0 +1 @@
+27b7ce66243eb1403862d05f958c002312df173d
diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b5 b/tests-clar/resources/push_src/.gitted/refs/heads/b5
new file mode 100644
index 000000000..cf313ad05
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/refs/heads/b5
@@ -0,0 +1 @@
+fa38b91f199934685819bea316186d8b008c52a2
diff --git a/tests-clar/resources/push_src/.gitted/refs/heads/b6 b/tests-clar/resources/push_src/.gitted/refs/heads/b6
new file mode 100644
index 000000000..711e466ae
--- /dev/null
+++ b/tests-clar/resources/push_src/.gitted/refs/heads/b6
@@ -0,0 +1 @@
+951bbbb90e2259a4c8950db78946784fb53fcbce
diff --git a/tests-clar/resources/push_src/a.txt b/tests-clar/resources/push_src/a.txt
new file mode 100644
index 000000000..f7eac1c51
--- /dev/null
+++ b/tests-clar/resources/push_src/a.txt
@@ -0,0 +1,2 @@
+a
+edit
diff --git a/tests-clar/resources/push_src/fold/b.txt b/tests-clar/resources/push_src/fold/b.txt
new file mode 100644
index 000000000..617807982
--- /dev/null
+++ b/tests-clar/resources/push_src/fold/b.txt
@@ -0,0 +1 @@
+b
diff --git a/tests-clar/resources/push_src/foldb.txt b/tests-clar/resources/push_src/foldb.txt
new file mode 100644
index 000000000..5b38718be
--- /dev/null
+++ b/tests-clar/resources/push_src/foldb.txt
@@ -0,0 +1 @@
+edit
diff --git a/tests-clar/resources/push_src/gitmodules b/tests-clar/resources/push_src/gitmodules
new file mode 100644
index 000000000..f1734dfc1
--- /dev/null
+++ b/tests-clar/resources/push_src/gitmodules
@@ -0,0 +1,3 @@
+[submodule "submodule"]
+ path = submodule
+ url = ../testrepo.git
diff --git a/tests-clar/resources/push_src/submodule/.gitted b/tests-clar/resources/push_src/submodule/.gitted
new file mode 100644
index 000000000..3ffcf960a
--- /dev/null
+++ b/tests-clar/resources/push_src/submodule/.gitted
@@ -0,0 +1 @@
+gitdir: ../.git/modules/submodule
diff --git a/tests-clar/resources/push_src/submodule/README b/tests-clar/resources/push_src/submodule/README
new file mode 100644
index 000000000..ca8c64728
--- /dev/null
+++ b/tests-clar/resources/push_src/submodule/README
@@ -0,0 +1 @@
+hey there
diff --git a/tests-clar/resources/push_src/submodule/branch_file.txt b/tests-clar/resources/push_src/submodule/branch_file.txt
new file mode 100644
index 000000000..a26902575
--- /dev/null
+++ b/tests-clar/resources/push_src/submodule/branch_file.txt
@@ -0,0 +1,2 @@
+hi
+bye!
diff --git a/tests-clar/resources/push_src/submodule/new.txt b/tests-clar/resources/push_src/submodule/new.txt
new file mode 100644
index 000000000..8e0884e36
--- /dev/null
+++ b/tests-clar/resources/push_src/submodule/new.txt
@@ -0,0 +1 @@
+my new file