summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml5
-rw-r--r--AUTHORS10
-rw-r--r--CMakeLists.txt41
-rw-r--r--Makefile.embed2
-rw-r--r--README.md37
-rw-r--r--deps/http-parser/http_parser.c1550
-rw-r--r--deps/http-parser/http_parser.h176
-rw-r--r--examples/Makefile2
-rw-r--r--examples/diff.c36
-rw-r--r--examples/general.c4
-rw-r--r--examples/network/Makefile5
-rw-r--r--examples/network/clone.c100
-rw-r--r--examples/network/common.h9
-rw-r--r--examples/network/fetch.c24
-rw-r--r--examples/network/index-pack.c10
-rw-r--r--examples/showindex.c84
-rw-r--r--include/git2.h2
-rw-r--r--include/git2/checkout.h172
-rw-r--r--include/git2/clone.h34
-rw-r--r--include/git2/config.h40
-rw-r--r--include/git2/diff.h138
-rw-r--r--include/git2/errors.h3
-rw-r--r--include/git2/index.h343
-rw-r--r--include/git2/indexer.h30
-rw-r--r--include/git2/odb.h21
-rw-r--r--include/git2/odb_backend.h26
-rw-r--r--include/git2/pack.h24
-rw-r--r--include/git2/reflog.h20
-rw-r--r--include/git2/refs.h284
-rw-r--r--include/git2/remote.h93
-rw-r--r--include/git2/repository.h26
-rw-r--r--include/git2/stash.h122
-rw-r--r--include/git2/status.h127
-rw-r--r--include/git2/tag.h2
-rw-r--r--include/git2/threads.h4
-rw-r--r--include/git2/transport.h306
-rw-r--r--include/git2/tree.h18
-rw-r--r--include/git2/types.h3
-rw-r--r--src/attr.c38
-rw-r--r--src/attr_file.h10
-rw-r--r--src/branch.c51
-rw-r--r--src/buffer.c28
-rw-r--r--src/buffer.h25
-rw-r--r--src/cache.c1
-rw-r--r--src/checkout.c648
-rw-r--r--src/clone.c85
-rw-r--r--src/common.h3
-rw-r--r--src/config.c162
-rw-r--r--src/config.h18
-rw-r--r--src/config_file.c57
-rw-r--r--src/diff.c622
-rw-r--r--src/diff.h13
-rw-r--r--src/diff_output.c130
-rw-r--r--src/diff_output.h6
-rw-r--r--src/diff_tform.c466
-rw-r--r--src/errors.c51
-rw-r--r--src/fetch.c362
-rw-r--r--src/fetch.h14
-rw-r--r--src/fetchhead.c126
-rw-r--r--src/fetchhead.h27
-rw-r--r--src/filebuf.c55
-rw-r--r--src/filebuf.h4
-rw-r--r--src/fileops.c231
-rw-r--r--src/fileops.h84
-rw-r--r--src/filter.c4
-rw-r--r--src/global.c51
-rw-r--r--src/global.h9
-rw-r--r--src/hash.c75
-rw-r--r--src/hash.h30
-rw-r--r--src/hash/hash_generic.c (renamed from src/sha1/sha1.c)30
-rw-r--r--src/hash/hash_generic.h24
-rw-r--r--src/hash/hash_openssl.h45
-rw-r--r--src/hash/hash_ppc.c (renamed from src/ppc/sha1.c)24
-rw-r--r--src/hash/hash_ppc.h (renamed from src/ppc/sha1.h)20
-rw-r--r--src/hash/hash_ppc_core.S (renamed from src/ppc/sha1ppc.S)4
-rw-r--r--src/hash/hash_win32.c291
-rw-r--r--src/hash/hash_win32.h140
-rw-r--r--src/index.c845
-rw-r--r--src/index.h15
-rw-r--r--src/indexer.c84
-rw-r--r--src/iterator.c80
-rw-r--r--src/iterator.h14
-rw-r--r--src/merge.c48
-rw-r--r--src/merge.h19
-rw-r--r--src/netops.c197
-rw-r--r--src/netops.h55
-rw-r--r--src/odb.c80
-rw-r--r--src/odb_pack.c67
-rw-r--r--src/pack-objects.c67
-rw-r--r--src/pack-objects.h15
-rw-r--r--src/path.c5
-rw-r--r--src/pathspec.c151
-rw-r--r--src/pathspec.h32
-rw-r--r--src/pkt.h91
-rw-r--r--src/posix.c2
-rw-r--r--src/protocol.c110
-rw-r--r--src/protocol.h21
-rw-r--r--src/reflog.c49
-rw-r--r--src/refs.c20
-rw-r--r--src/refs.h12
-rw-r--r--src/refspec.c18
-rw-r--r--src/refspec.h10
-rw-r--r--src/remote.c682
-rw-r--r--src/remote.h7
-rw-r--r--src/repository.c173
-rw-r--r--src/reset.c22
-rw-r--r--src/revparse.c8
-rw-r--r--src/sha1.h33
-rw-r--r--src/stash.c660
-rw-r--r--src/status.c89
-rw-r--r--src/submodule.c14
-rw-r--r--src/tag.c19
-rw-r--r--src/transport.c91
-rw-r--r--src/transport.h148
-rw-r--r--src/transports/cred.c57
-rw-r--r--src/transports/git.c290
-rw-r--r--src/transports/http.c929
-rw-r--r--src/transports/local.c281
-rw-r--r--src/transports/smart.c284
-rw-r--r--src/transports/smart.h150
-rw-r--r--src/transports/smart_pkt.c (renamed from src/pkt.c)25
-rw-r--r--src/transports/smart_protocol.c481
-rw-r--r--src/transports/winhttp.c634
-rw-r--r--src/tree.c24
-rw-r--r--src/tree.h6
-rw-r--r--src/unix/posix.h4
-rw-r--r--src/util.c41
-rw-r--r--src/util.h21
-rw-r--r--src/vector.c44
-rw-r--r--src/vector.h13
-rw-r--r--src/win32/posix.h9
-rw-r--r--src/win32/posix_w32.c165
-rw-r--r--src/win32/utf-conv.c8
-rw-r--r--src/win32/utf-conv.h4
-rw-r--r--tests-clar/attr/repo.c4
-rw-r--r--tests-clar/buf/splice.c93
-rw-r--r--tests-clar/checkout/head.c2
-rw-r--r--tests-clar/checkout/index.c152
-rw-r--r--tests-clar/checkout/tree.c29
-rw-r--r--tests-clar/checkout/typechange.c15
-rw-r--r--tests-clar/clar_helpers.c104
-rw-r--r--tests-clar/clar_libgit2.h7
-rw-r--r--tests-clar/clone/network.c31
-rw-r--r--tests-clar/clone/nonetwork.c63
-rw-r--r--tests-clar/config/config_helpers.c37
-rw-r--r--tests-clar/config/config_helpers.h9
-rw-r--r--tests-clar/config/configlevel.c12
-rw-r--r--tests-clar/config/new.c1
-rw-r--r--tests-clar/config/read.c31
-rw-r--r--tests-clar/config/refresh.c67
-rw-r--r--tests-clar/core/copy.c8
-rw-r--r--tests-clar/core/env.c24
-rw-r--r--tests-clar/core/mkdir.c14
-rw-r--r--tests-clar/core/rmdir.c42
-rw-r--r--tests-clar/core/stat.c97
-rw-r--r--tests-clar/core/vector.c84
-rw-r--r--tests-clar/diff/blob.c76
-rw-r--r--tests-clar/diff/diff_helpers.c17
-rw-r--r--tests-clar/diff/diff_helpers.h9
-rw-r--r--tests-clar/diff/diffiter.c115
-rw-r--r--tests-clar/diff/index.c18
-rw-r--r--tests-clar/diff/iterator.c2
-rw-r--r--tests-clar/diff/patch.c32
-rw-r--r--tests-clar/diff/rename.c105
-rw-r--r--tests-clar/diff/tree.c66
-rw-r--r--tests-clar/diff/workdir.c187
-rw-r--r--tests-clar/fetchhead/fetchhead_data.h21
-rw-r--r--tests-clar/fetchhead/network.c87
-rw-r--r--tests-clar/fetchhead/nonetwork.c96
-rw-r--r--tests-clar/index/conflicts.c221
-rw-r--r--tests-clar/index/filemodes.c66
-rw-r--r--tests-clar/index/inmemory.c22
-rw-r--r--tests-clar/index/read_tree.c12
-rw-r--r--tests-clar/index/rename.c10
-rw-r--r--tests-clar/index/reuc.c236
-rw-r--r--tests-clar/index/stage.c60
-rw-r--r--tests-clar/index/tests.c48
-rw-r--r--tests-clar/network/fetch.c17
-rw-r--r--tests-clar/network/fetchlocal.c65
-rw-r--r--tests-clar/network/remotelocal.c42
-rw-r--r--tests-clar/network/remoterename.c201
-rw-r--r--tests-clar/network/remotes.c44
-rw-r--r--tests-clar/object/blob/write.c4
-rw-r--r--tests-clar/object/commit/commitstagedfile.c71
-rw-r--r--tests-clar/object/message.c65
-rw-r--r--tests-clar/object/raw/hash.c16
-rw-r--r--tests-clar/object/raw/short.c2
-rw-r--r--tests-clar/object/tag/read.c35
-rw-r--r--tests-clar/object/tree/duplicateentries.c157
-rw-r--r--tests-clar/object/tree/write.c6
-rw-r--r--tests-clar/odb/alternates.c75
-rw-r--r--tests-clar/pack/packbuilder.c31
-rw-r--r--tests-clar/refs/branches/delete.c15
-rw-r--r--tests-clar/refs/branches/ishead.c16
-rw-r--r--tests-clar/refs/branches/move.c22
-rw-r--r--tests-clar/refs/reflog/drop.c39
-rw-r--r--tests-clar/refs/reflog/reflog.c4
-rw-r--r--tests-clar/repo/discover.c2
-rw-r--r--tests-clar/repo/getters.c30
-rw-r--r--tests-clar/repo/head.c10
-rw-r--r--tests-clar/repo/open.c6
-rw-r--r--tests-clar/repo/repo_helpers.c11
-rw-r--r--tests-clar/repo/repo_helpers.h1
-rw-r--r--tests-clar/repo/state.c96
-rw-r--r--tests-clar/reset/hard.c108
-rw-r--r--tests-clar/reset/soft.c18
-rw-r--r--tests-clar/resources/config/config112
-rw-r--r--tests-clar/resources/config/config42
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG1
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/HEAD1
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD1
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/MERGE_MODE0
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/MERGE_MSG5
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD1
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/config6
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/description1
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/indexbin0 -> 842 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/info/exclude6
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/logs/HEAD5
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch2
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master2
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712bin0 -> 141 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91bin0 -> 141 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81bin0 -> 34 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211ebin0 -> 42 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6bin0 -> 159 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534bin0 -> 74 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c8762
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8cbin0 -> 39 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3bin0 -> 36 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52dabin0 -> 36 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46adbin0 -> 45 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399bin0 -> 49 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8bin0 -> 85 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4bin0 -> 41 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71bin0 -> 41 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038cbin0 -> 40 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cdabin0 -> 66 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673bin0 -> 38 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735abin0 -> 78 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2bin0 -> 57 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8bin0 -> 38 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9bin0 -> 39 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50bin0 -> 140 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c51
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092cbin0 -> 36 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0dbin0 -> 66 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51bin0 -> 55 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff3
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478bin0 -> 87 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406bin0 -> 141 bytes
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/refs/heads/branch1
-rw-r--r--tests-clar/resources/mergedrepo/.gitted/refs/heads/master1
-rw-r--r--tests-clar/resources/mergedrepo/conflicts-one.txt5
-rw-r--r--tests-clar/resources/mergedrepo/conflicts-two.txt5
-rw-r--r--tests-clar/resources/mergedrepo/one.txt10
-rw-r--r--tests-clar/resources/mergedrepo/two.txt12
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/HEAD1
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/config7
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/indexbin0 -> 328 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08bin0 -> 19 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0ebin0 -> 163 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925bin0 -> 147 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7bin0 -> 51 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccdbin0 -> 119 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057bin0 -> 18 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd20452
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63bin0 -> 50 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf36442
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dccbin0 -> 50 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735bin0 -> 19 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60abin0 -> 119 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344dbin0 -> 82 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479bin0 -> 126 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a3
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f2
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bdbin0 -> 28 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6bin0 -> 26 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd3
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9bin0 -> 162 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83bin0 -> 147 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1bin0 -> 82 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92bin0 -> 24 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765bin0 -> 82 bytes
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep0
-rw-r--r--tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir1
-rw-r--r--tests-clar/resources/renames/.gitted/HEAD1
-rw-r--r--tests-clar/resources/renames/.gitted/config7
-rw-r--r--tests-clar/resources/renames/.gitted/description1
-rw-r--r--tests-clar/resources/renames/.gitted/indexbin0 -> 272 bytes
-rw-r--r--tests-clar/resources/renames/.gitted/info/exclude6
-rw-r--r--tests-clar/resources/renames/.gitted/logs/HEAD2
-rw-r--r--tests-clar/resources/renames/.gitted/logs/refs/heads/master2
-rw-r--r--tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7bin0 -> 90 bytes
-rw-r--r--tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49abin0 -> 173 bytes
-rw-r--r--tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2bin0 -> 131 bytes
-rw-r--r--tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745bin0 -> 106 bytes
-rw-r--r--tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bbabin0 -> 1155 bytes
-rw-r--r--tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113bin0 -> 415 bytes
-rw-r--r--tests-clar/resources/renames/.gitted/refs/heads/master1
-rw-r--r--tests-clar/resources/renames/sevencities.txt49
-rw-r--r--tests-clar/resources/renames/sixserving.txt24
-rw-r--r--tests-clar/resources/renames/songofseven.txt49
-rw-r--r--tests-clar/resources/short_tag.git/HEAD1
-rw-r--r--tests-clar/resources/short_tag.git/config5
-rw-r--r--tests-clar/resources/short_tag.git/indexbin0 -> 104 bytes
-rw-r--r--tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93cbin0 -> 169 bytes
-rw-r--r--tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441cbin0 -> 48 bytes
-rw-r--r--tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb7291
-rw-r--r--tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391bin0 -> 15 bytes
-rw-r--r--tests-clar/resources/short_tag.git/packed-refs1
-rw-r--r--tests-clar/resources/short_tag.git/refs/heads/master1
-rw-r--r--tests-clar/resources/testrepo.git/config2
-rw-r--r--tests-clar/stash/drop.c126
-rw-r--r--tests-clar/stash/foreach.c120
-rw-r--r--tests-clar/stash/save.c370
-rw-r--r--tests-clar/stash/stash_helpers.c68
-rw-r--r--tests-clar/stash/stash_helpers.h8
-rw-r--r--tests-clar/status/worktree.c16
-rw-r--r--tests-clar/submodule/status.c10
320 files changed, 15308 insertions, 4820 deletions
diff --git a/.travis.yml b/.travis.yml
index e8f37d229..256227589 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -46,8 +46,3 @@ notifications:
- irc.freenode.net#libgit2
on_success: change
on_failure: always
- recipients:
- - vicent@github.com
- email:
- on_success: change
- on_failure: always
diff --git a/AUTHORS b/AUTHORS
index 4f91758d9..7f6ea7756 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -4,6 +4,7 @@ to the libgit2 project (sorted alphabetically):
Alex Budovski
Alexei Sholik
Andreas Ericsson
+Anton "antong" Gyllenberg
Ankur Sethi
Ben Noordhuis
Ben Straub
@@ -32,12 +33,15 @@ Jonathan "Duke" Leto
Julien Miotte
Julio Espinoza-Sokal
Justin Love
+Kelly "kelly.leahy" Leahy
Kirill A. Shutemov
Lambert CLARA
Luc Bertrand
Marc Pegon
Marcel Groothuis
Marco Villegas
+Michael "schu" Schubert
+Microsoft Corporation
Olivier Ramonat
Peter Drahoš
Pierre Habouzit
@@ -48,8 +52,9 @@ Romain Geissler
Romain Muller
Russell Belfer
Sakari Jokinen
-Sam
+Samuel Charles "Sam" Day
Sarath Lakshman
+Sascha Cunz
Sascha Peilicke
Scott Chacon
Sebastian Schuberth
@@ -62,6 +67,3 @@ Tim Clem
Tim Harder
Trent Mick
Vicent Marti
-antong
-kelly.leahy
-schu
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a57394640..17e4ff2de 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,11 +39,14 @@ ENDIF()
# Specify sha1 implementation
IF (SHA1_TYPE STREQUAL "ppc")
ADD_DEFINITIONS(-DPPC_SHA1)
- FILE(GLOB SRC_SHA1 src/ppc/*.c src/ppc/*.S)
-ELSEIF (OPENSSL_FOUND) # libcrypto's implementation is faster than ours
- ADD_DEFINITIONS(-DOPENSSL_SHA)
-ELSE ()
- FILE(GLOB SRC_SHA1 src/sha1/*.c)
+ FILE(GLOB SRC_SHA1 src/hash/hash_ppc.c src/hash/hash_ppc_core.S)
+ELSEIF (WIN32 AND NOT MINGW AND NOT SHA1_TYPE STREQUAL "builtin")
+ ADD_DEFINITIONS(-DWIN32_SHA1)
+ FILE(GLOB SRC_SHA1 src/hash/hash_win32.c)
+ELSEIF (OPENSSL_FOUND AND NOT SHA1_TYPE STREQUAL "builtin")
+ ADD_DEFINITIONS(-DOPENSSL_SHA1)
+ELSE()
+ FILE(GLOB SRC_SHA1 src/hash/hash_generic.c)
ENDIF()
IF (NOT WIN32)
@@ -85,7 +88,9 @@ IF (MSVC)
# Default to stdcall, as that's what the CLR expects and how the Windows API is built
OPTION (STDCALL "Buildl libgit2 with the __stdcall convention" ON)
- SET(CMAKE_C_FLAGS "/W4 /MP /nologo /Zi ${CMAKE_C_FLAGS}")
+ STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
+
+ SET(CMAKE_C_FLAGS "/MP /nologo /Zi ${CMAKE_C_FLAGS}")
IF (STDCALL)
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gz")
ENDIF ()
@@ -94,24 +99,32 @@ IF (MSVC)
SET(WIN_RC "src/win32/git2.rc")
# Precompiled headers
+
ELSE ()
- SET(CMAKE_C_FLAGS_DEBUG "-O0 -g ${CMAKE_C_FLAGS}")
- SET(CMAKE_C_FLAGS "-O2 -g -D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes -Wmissing-prototypes ${CMAKE_C_FLAGS}")
+ SET(CMAKE_C_FLAGS "-D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes -Wmissing-prototypes ${CMAKE_C_FLAGS}")
IF (MINGW) # MinGW always does PIC and complains if we tell it to
STRING(REGEX REPLACE "-fPIC" "" CMAKE_SHARED_LIBRARY_C_FLAGS "${CMAKE_SHARED_LIBRARY_C_FLAGS}")
ELSE ()
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -fPIC")
ENDIF ()
+ IF (APPLE) # Apple deprecated OpenSSL
+ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations")
+ ENDIF ()
IF (PROFILE)
SET(CMAKE_C_FLAGS "-pg ${CMAKE_C_FLAGS}")
SET(CMAKE_EXE_LINKER_FLAGS "-pg ${CMAKE_EXE_LINKER_FLAGS}")
ENDIF ()
ENDIF()
-# Build Debug by default
-IF (NOT CMAKE_BUILD_TYPE)
- SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
-ENDIF ()
+IF( NOT CMAKE_CONFIGURATION_TYPES )
+ # Build Debug by default
+ IF (NOT CMAKE_BUILD_TYPE)
+ SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
+ ENDIF ()
+ELSE()
+ # Using a multi-configuration generator eg MSVC or Xcode
+ # that uses CMAKE_CONFIGURATION_TYPES and not CMAKE_BUILD_TYPE
+ENDIF()
IF (OPENSSL_FOUND)
ADD_DEFINITIONS(-DGIT_SSL)
@@ -159,7 +172,7 @@ SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING})
SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR})
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY)
-IF (MSVC)
+IF (MSVC_IDE)
# Precompiled headers
SET_TARGET_PROPERTIES(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
SET_SOURCE_FILES_PROPERTIES(src/win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h")
@@ -198,7 +211,7 @@ IF (BUILD_CLAR)
ADD_EXECUTABLE(libgit2_clar ${SRC} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1})
TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES})
- IF (MSVC)
+ IF (MSVC_IDE)
# Precompiled headers
SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
ENDIF ()
diff --git a/Makefile.embed b/Makefile.embed
index b31a06e4b..76b4d3cda 100644
--- a/Makefile.embed
+++ b/Makefile.embed
@@ -15,7 +15,7 @@ INCLUDES= -I. -Isrc -Iinclude -Ideps/http-parser -Ideps/zlib
DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(EXTRA_DEFINES)
CFLAGS= -g $(DEFINES) -Wall -Wextra -O2 $(EXTRA_CFLAGS)
-SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) $(wildcard src/sha1/*.c)
+SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) src/hash/hash_generic.c
ifeq ($(PLATFORM),Msys)
SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c
diff --git a/README.md b/README.md
index 08687276e..062fc818a 100644
--- a/README.md
+++ b/README.md
@@ -58,10 +58,6 @@ To install the library you can specify the install prefix by setting:
$ cmake .. -DCMAKE_INSTALL_PREFIX=/install/prefix
$ cmake --build . --target install
-If you want to build a universal binary for Mac OS X, CMake sets it
-all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"`
-when configuring.
-
For more advanced use or questions about CMake please read <http://www.cmake.org/Wiki/CMake_FAQ>.
The following CMake variables are declared:
@@ -70,8 +66,39 @@ The following CMake variables are declared:
- `LIB_INSTALL_DIR`: Where to install libraries to.
- `INCLUDE_INSTALL_DIR`: Where to install headers to.
- `BUILD_SHARED_LIBS`: Build libgit2 as a Shared Library (defaults to ON)
-- `BUILD_CLAR`: Build [Clar](https://github.com/tanoku/clar)-based test suite (defaults to ON)
+- `BUILD_CLAR`: Build [Clar](https://github.com/vmg/clar)-based test suite (defaults to ON)
- `THREADSAFE`: Build libgit2 with threading support (defaults to OFF)
+- `STDCALL`: Build libgit2 as `stdcall`. Turn off for `cdecl` (Windows; defaults to ON)
+
+Compiler and linker options
+---------------------------
+
+CMake lets you specify a few variables to control the behavior of the
+compiler and linker. These flags are rarely used but can be useful for
+64-bit to 32-bit cross-compilation.
+
+- `CMAKE_C_FLAGS`: Set your own compiler flags
+- `CMAKE_FIND_ROOT_PATH`: Override the search path for libraries
+- `ZLIB_LIBRARY`, `OPENSSL_SSL_LIBRARY` AND `OPENSSL_CRYPTO_LIBRARY`:
+Tell CMake where to find those specific libraries
+
+MacOS X
+-------
+
+If you want to build a universal binary for Mac OS X, CMake sets it
+all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"`
+when configuring.
+
+Windows
+-------
+
+You need to run the CMake commands from the Visual Studio command
+prompt, not the regular or Windows SDK one. Select the right generator
+for your version with the `-G "Visual Studio X" option.
+
+See [the wiki]
+(https://github.com/libgit2/libgit2/wiki/Building-libgit2-on-Windows)
+for more detailed instructions.
Language Bindings
==================================
diff --git a/deps/http-parser/http_parser.c b/deps/http-parser/http_parser.c
index 438e81bec..b13a28c58 100644
--- a/deps/http-parser/http_parser.c
+++ b/deps/http-parser/http_parser.c
@@ -21,61 +21,100 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
-#include <http_parser.h>
+#include "http_parser.h"
#include <assert.h>
#include <stddef.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#ifndef ULLONG_MAX
+# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
+#endif
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
+#ifndef ARRAY_SIZE
+# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#endif
+
+#ifndef BIT_AT
+# define BIT_AT(a, i) \
+ (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
+ (1 << ((unsigned int) (i) & 7))))
+#endif
+
+#ifndef ELEM_AT
+# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
+#endif
-#if HTTP_PARSER_DEBUG
-#define SET_ERRNO(e) \
-do { \
- parser->http_errno = (e); \
- parser->error_lineno = __LINE__; \
-} while (0)
-#else
#define SET_ERRNO(e) \
do { \
parser->http_errno = (e); \
} while(0)
-#endif
-#define CALLBACK2(FOR) \
+/* Run the notify callback FOR, returning ER if it fails */
+#define CALLBACK_NOTIFY_(FOR, ER) \
do { \
+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
+ \
if (settings->on_##FOR) { \
if (0 != settings->on_##FOR(parser)) { \
SET_ERRNO(HPE_CB_##FOR); \
- return (p - data); \
+ } \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
+ return (ER); \
} \
} \
} while (0)
+/* Run the notify callback FOR and consume the current byte */
+#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1)
-#define MARK(FOR) \
-do { \
- FOR##_mark = p; \
-} while (0)
+/* Run the notify callback FOR and don't consume the current byte */
+#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data)
-#define CALLBACK(FOR) \
+/* Run data callback FOR with LEN bytes, returning ER if it fails */
+#define CALLBACK_DATA_(FOR, LEN, ER) \
do { \
+ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
+ \
if (FOR##_mark) { \
if (settings->on_##FOR) { \
- if (0 != settings->on_##FOR(parser, \
- FOR##_mark, \
- p - FOR##_mark)) \
- { \
+ if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \
SET_ERRNO(HPE_CB_##FOR); \
- return (p - data); \
+ } \
+ \
+ /* We either errored above or got paused; get out */ \
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
+ return (ER); \
} \
} \
FOR##_mark = NULL; \
} \
} while (0)
+
+/* Run the data callback FOR and consume the current byte */
+#define CALLBACK_DATA(FOR) \
+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
+
+/* Run the data callback FOR and don't consume the current byte */
+#define CALLBACK_DATA_NOADVANCE(FOR) \
+ CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
+
+/* Set the mark FOR; non-destructive if mark is already set */
+#define MARK(FOR) \
+do { \
+ if (!FOR##_mark) { \
+ FOR##_mark = p; \
+ } \
+} while (0)
#define PROXY_CONNECTION "proxy-connection"
@@ -89,30 +128,10 @@ do { \
static const char *method_strings[] =
- { "DELETE"
- , "GET"
- , "HEAD"
- , "POST"
- , "PUT"
- , "CONNECT"
- , "OPTIONS"
- , "TRACE"
- , "COPY"
- , "LOCK"
- , "MKCOL"
- , "MOVE"
- , "PROPFIND"
- , "PROPPATCH"
- , "UNLOCK"
- , "REPORT"
- , "MKACTIVITY"
- , "CHECKOUT"
- , "MERGE"
- , "M-SEARCH"
- , "NOTIFY"
- , "SUBSCRIBE"
- , "UNSUBSCRIBE"
- , "PATCH"
+ {
+#define XX(num, name, string) #string,
+ HTTP_METHOD_MAP(XX)
+#undef XX
};
@@ -133,9 +152,9 @@ static const char tokens[256] = {
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
0, 0, 0, 0, 0, 0, 0, 0,
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
- ' ', '!', '"', '#', '$', '%', '&', '\'',
+ 0, '!', 0, '#', '$', '%', '&', '\'',
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
- 0, 0, '*', '+', 0, '-', '.', '/',
+ 0, 0, '*', '+', 0, '-', '.', 0,
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
'0', '1', '2', '3', '4', '5', '6', '7',
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
@@ -155,7 +174,7 @@ static const char tokens[256] = {
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
- 'x', 'y', 'z', 0, '|', '}', '~', 0 };
+ 'x', 'y', 'z', 0, '|', 0, '~', 0 };
static const int8_t unhex[256] =
@@ -170,40 +189,48 @@ static const int8_t unhex[256] =
};
-static const uint8_t normal_url_char[256] = {
+#if HTTP_PARSER_STRICT
+# define T(v) 0
+#else
+# define T(v) v
+#endif
+
+
+static const uint8_t normal_url_char[32] = {
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
- 0, 0, 0, 0, 0, 0, 0, 0,
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
- 0, 0, 0, 0, 0, 0, 0, 0,
+ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
- 0, 0, 0, 0, 0, 0, 0, 0,
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
- 0, 0, 0, 0, 0, 0, 0, 0,
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
- 0, 1, 1, 0, 1, 1, 1, 1,
+ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
- 1, 1, 1, 1, 1, 1, 1, 0,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
- 1, 1, 1, 1, 1, 1, 1, 1,
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
- 1, 1, 1, 1, 1, 1, 1, 0, };
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
+#undef T
enum state
{ s_dead = 1 /* important that this is > 0 */
@@ -231,8 +258,9 @@ enum state
, s_req_schema
, s_req_schema_slash
, s_req_schema_slash_slash
- , s_req_host
- , s_req_port
+ , s_req_server_start
+ , s_req_server
+ , s_req_server_with_at
, s_req_path
, s_req_query_string_start
, s_req_query_string
@@ -261,9 +289,11 @@ enum state
, s_chunk_size
, s_chunk_parameters
, s_chunk_size_almost_done
-
+
, s_headers_almost_done
- /* Important: 's_headers_almost_done' must be the last 'header' state. All
+ , s_headers_done
+
+ /* Important: 's_headers_done' must be the last 'header' state. All
* states beyond this must be 'body' states. It is used for overflow
* checking. See the PARSING_HEADER() macro.
*/
@@ -274,10 +304,12 @@ enum state
, s_body_identity
, s_body_identity_eof
+
+ , s_message_done
};
-#define PARSING_HEADER(state) (state <= s_headers_almost_done)
+#define PARSING_HEADER(state) (state <= s_headers_done)
enum header_states
@@ -306,22 +338,43 @@ enum header_states
, h_connection_close
};
+enum http_host_state
+ {
+ s_http_host_dead = 1
+ , s_http_userinfo_start
+ , s_http_userinfo
+ , s_http_host_start
+ , s_http_host_v6_start
+ , s_http_host
+ , s_http_host_v6
+ , s_http_host_v6_end
+ , s_http_host_port_start
+ , s_http_host_port
+};
/* Macros for character classes; depends on strict-mode */
#define CR '\r'
#define LF '\n'
#define LOWER(c) (unsigned char)(c | 0x20)
-#define TOKEN(c) (tokens[(unsigned char)c])
#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z')
#define IS_NUM(c) ((c) >= '0' && (c) <= '9')
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
+#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
+#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
+ (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
+ (c) == ')')
+#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
+ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+ (c) == '$' || (c) == ',')
#if HTTP_PARSER_STRICT
-#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)])
+#define TOKEN(c) (tokens[(unsigned char)c])
+#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
#else
+#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
#define IS_URL_CHAR(c) \
- (normal_url_char[(unsigned char) (c)] || ((c) & 0x80))
+ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
#define IS_HOST_CHAR(c) \
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
#endif
@@ -355,6 +408,166 @@ static struct {
};
#undef HTTP_STRERROR_GEN
+int http_message_needs_eof(const http_parser *parser);
+
+/* Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+static enum state
+parse_url_char(enum state s, const char ch)
+{
+ if (ch == ' ' || ch == '\r' || ch == '\n') {
+ return s_dead;
+ }
+
+#if HTTP_PARSER_STRICT
+ if (ch == '\t' || ch == '\f') {
+ return s_dead;
+ }
+#endif
+
+ switch (s) {
+ case s_req_spaces_before_url:
+ /* Proxied requests are followed by scheme of an absolute URI (alpha).
+ * All methods except CONNECT are followed by '/' or '*'.
+ */
+
+ if (ch == '/' || ch == '*') {
+ return s_req_path;
+ }
+
+ if (IS_ALPHA(ch)) {
+ return s_req_schema;
+ }
+
+ break;
+
+ case s_req_schema:
+ if (IS_ALPHA(ch)) {
+ return s;
+ }
+
+ if (ch == ':') {
+ return s_req_schema_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash:
+ if (ch == '/') {
+ return s_req_schema_slash_slash;
+ }
+
+ break;
+
+ case s_req_schema_slash_slash:
+ if (ch == '/') {
+ return s_req_server_start;
+ }
+
+ break;
+
+ case s_req_server_with_at:
+ if (ch == '@') {
+ return s_dead;
+ }
+
+ /* FALLTHROUGH */
+ case s_req_server_start:
+ case s_req_server:
+ if (ch == '/') {
+ return s_req_path;
+ }
+
+ if (ch == '?') {
+ return s_req_query_string_start;
+ }
+
+ if (ch == '@') {
+ return s_req_server_with_at;
+ }
+
+ if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+ return s_req_server;
+ }
+
+ break;
+
+ case s_req_path:
+ if (IS_URL_CHAR(ch)) {
+ return s;
+ }
+
+ switch (ch) {
+ case '?':
+ return s_req_query_string_start;
+
+ case '#':
+ return s_req_fragment_start;
+ }
+
+ break;
+
+ case s_req_query_string_start:
+ case s_req_query_string:
+ if (IS_URL_CHAR(ch)) {
+ return s_req_query_string;
+ }
+
+ switch (ch) {
+ case '?':
+ /* allow extra '?' in query string */
+ return s_req_query_string;
+
+ case '#':
+ return s_req_fragment_start;
+ }
+
+ break;
+
+ case s_req_fragment_start:
+ if (IS_URL_CHAR(ch)) {
+ return s_req_fragment;
+ }
+
+ switch (ch) {
+ case '?':
+ return s_req_fragment;
+
+ case '#':
+ return s;
+ }
+
+ break;
+
+ case s_req_fragment:
+ if (IS_URL_CHAR(ch)) {
+ return s;
+ }
+
+ switch (ch) {
+ case '?':
+ case '#':
+ return s;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* We should never fall out of the switch above unless there's an error */
+ return s_dead;
+}
size_t http_parser_execute (http_parser *parser,
const http_parser_settings *settings,
@@ -363,27 +576,24 @@ size_t http_parser_execute (http_parser *parser,
{
char c, ch;
int8_t unhex_val;
- const char *p = data, *pe;
- size_t to_read;
- enum state state;
- enum header_states header_state;
- size_t index = parser->index;
- size_t nread = parser->nread;
- const char *header_field_mark, *header_value_mark, *url_mark;
- const char *matcher;
+ const char *p = data;
+ const char *header_field_mark = 0;
+ const char *header_value_mark = 0;
+ const char *url_mark = 0;
+ const char *body_mark = 0;
/* We're in an error state. Don't bother doing anything. */
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
return 0;
}
- state = (enum state) parser->state;
- header_state = (enum header_states) parser->header_state;
-
if (len == 0) {
- switch (state) {
+ switch (parser->state) {
case s_body_identity_eof:
- CALLBACK2(message_complete);
+ /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
+ * we got paused.
+ */
+ CALLBACK_NOTIFY_NOADVANCE(message_complete);
return 0;
case s_dead:
@@ -398,42 +608,49 @@ size_t http_parser_execute (http_parser *parser,
}
}
- /* technically we could combine all of these (except for url_mark) into one
- variable, saving stack space, but it seems more clear to have them
- separated. */
- header_field_mark = 0;
- header_value_mark = 0;
- url_mark = 0;
- if (state == s_header_field)
+ if (parser->state == s_header_field)
header_field_mark = data;
- if (state == s_header_value)
+ if (parser->state == s_header_value)
header_value_mark = data;
- if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash
- || state == s_req_schema_slash_slash || state == s_req_port
- || state == s_req_query_string_start || state == s_req_query_string
- || state == s_req_host
- || state == s_req_fragment_start || state == s_req_fragment)
+ switch (parser->state) {
+ case s_req_path:
+ case s_req_schema:
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_query_string_start:
+ case s_req_query_string:
+ case s_req_fragment_start:
+ case s_req_fragment:
url_mark = data;
+ break;
+ }
- for (p=data, pe=data+len; p != pe; p++) {
+ for (p=data; p != data + len; p++) {
ch = *p;
- if (PARSING_HEADER(state)) {
- ++nread;
+ if (PARSING_HEADER(parser->state)) {
+ ++parser->nread;
/* Buffer overflow attack */
- if (nread > HTTP_MAX_HEADER_SIZE) {
+ if (parser->nread > HTTP_MAX_HEADER_SIZE) {
SET_ERRNO(HPE_HEADER_OVERFLOW);
goto error;
}
}
- switch (state) {
+ reexecute_byte:
+ switch (parser->state) {
case s_dead:
/* this state is used after a 'Connection: close' message
* the parser will error out if it reads another message
*/
+ if (ch == CR || ch == LF)
+ break;
+
SET_ERRNO(HPE_CLOSED_CONNECTION);
goto error;
@@ -442,23 +659,25 @@ size_t http_parser_execute (http_parser *parser,
if (ch == CR || ch == LF)
break;
parser->flags = 0;
- parser->content_length = -1;
+ parser->content_length = ULLONG_MAX;
- CALLBACK2(message_begin);
+ if (ch == 'H') {
+ parser->state = s_res_or_resp_H;
- if (ch == 'H')
- state = s_res_or_resp_H;
- else {
+ CALLBACK_NOTIFY(message_begin);
+ } else {
parser->type = HTTP_REQUEST;
- goto start_req_method_assign;
+ parser->state = s_start_req;
+ goto reexecute_byte;
}
+
break;
}
case s_res_or_resp_H:
if (ch == 'T') {
parser->type = HTTP_RESPONSE;
- state = s_res_HT;
+ parser->state = s_res_HT;
} else {
if (ch != 'E') {
SET_ERRNO(HPE_INVALID_CONSTANT);
@@ -467,21 +686,19 @@ size_t http_parser_execute (http_parser *parser,
parser->type = HTTP_REQUEST;
parser->method = HTTP_HEAD;
- index = 2;
- state = s_req_method;
+ parser->index = 2;
+ parser->state = s_req_method;
}
break;
case s_start_res:
{
parser->flags = 0;
- parser->content_length = -1;
-
- CALLBACK2(message_begin);
+ parser->content_length = ULLONG_MAX;
switch (ch) {
case 'H':
- state = s_res_H;
+ parser->state = s_res_H;
break;
case CR:
@@ -492,44 +709,46 @@ size_t http_parser_execute (http_parser *parser,
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
+
+ CALLBACK_NOTIFY(message_begin);
break;
}
case s_res_H:
STRICT_CHECK(ch != 'T');
- state = s_res_HT;
+ parser->state = s_res_HT;
break;
case s_res_HT:
STRICT_CHECK(ch != 'T');
- state = s_res_HTT;
+ parser->state = s_res_HTT;
break;
case s_res_HTT:
STRICT_CHECK(ch != 'P');
- state = s_res_HTTP;
+ parser->state = s_res_HTTP;
break;
case s_res_HTTP:
STRICT_CHECK(ch != '/');
- state = s_res_first_http_major;
+ parser->state = s_res_first_http_major;
break;
case s_res_first_http_major:
- if (ch < '1' || ch > '9') {
+ if (ch < '0' || ch > '9') {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_major = ch - '0';
- state = s_res_http_major;
+ parser->state = s_res_http_major;
break;
/* major HTTP version or dot */
case s_res_http_major:
{
if (ch == '.') {
- state = s_res_first_http_minor;
+ parser->state = s_res_first_http_minor;
break;
}
@@ -557,14 +776,14 @@ size_t http_parser_execute (http_parser *parser,
}
parser->http_minor = ch - '0';
- state = s_res_http_minor;
+ parser->state = s_res_http_minor;
break;
/* minor HTTP version or end of request line */
case s_res_http_minor:
{
if (ch == ' ') {
- state = s_res_first_status_code;
+ parser->state = s_res_first_status_code;
break;
}
@@ -595,7 +814,7 @@ size_t http_parser_execute (http_parser *parser,
goto error;
}
parser->status_code = ch - '0';
- state = s_res_status_code;
+ parser->state = s_res_status_code;
break;
}
@@ -604,13 +823,13 @@ size_t http_parser_execute (http_parser *parser,
if (!IS_NUM(ch)) {
switch (ch) {
case ' ':
- state = s_res_status;
+ parser->state = s_res_status;
break;
case CR:
- state = s_res_line_almost_done;
+ parser->state = s_res_line_almost_done;
break;
case LF:
- state = s_header_field_start;
+ parser->state = s_header_field_start;
break;
default:
SET_ERRNO(HPE_INVALID_STATUS);
@@ -634,19 +853,19 @@ size_t http_parser_execute (http_parser *parser,
/* the human readable status. e.g. "NOT FOUND"
* we are not humans so just ignore this */
if (ch == CR) {
- state = s_res_line_almost_done;
+ parser->state = s_res_line_almost_done;
break;
}
if (ch == LF) {
- state = s_header_field_start;
+ parser->state = s_header_field_start;
break;
}
break;
case s_res_line_almost_done:
STRICT_CHECK(ch != LF);
- state = s_header_field_start;
+ parser->state = s_header_field_start;
break;
case s_start_req:
@@ -654,18 +873,15 @@ size_t http_parser_execute (http_parser *parser,
if (ch == CR || ch == LF)
break;
parser->flags = 0;
- parser->content_length = -1;
-
- CALLBACK2(message_begin);
+ parser->content_length = ULLONG_MAX;
if (!IS_ALPHA(ch)) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
- start_req_method_assign:
parser->method = (enum http_method) 0;
- index = 1;
+ parser->index = 1;
switch (ch) {
case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
case 'D': parser->method = HTTP_DELETE; break;
@@ -676,341 +892,158 @@ size_t http_parser_execute (http_parser *parser,
case 'N': parser->method = HTTP_NOTIFY; break;
case 'O': parser->method = HTTP_OPTIONS; break;
case 'P': parser->method = HTTP_POST;
- /* or PROPFIND or PROPPATCH or PUT or PATCH */
+ /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
break;
case 'R': parser->method = HTTP_REPORT; break;
- case 'S': parser->method = HTTP_SUBSCRIBE; break;
+ case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;
case 'T': parser->method = HTTP_TRACE; break;
case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
default:
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
- state = s_req_method;
+ parser->state = s_req_method;
+
+ CALLBACK_NOTIFY(message_begin);
+
break;
}
case s_req_method:
{
+ const char *matcher;
if (ch == '\0') {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
matcher = method_strings[parser->method];
- if (ch == ' ' && matcher[index] == '\0') {
- state = s_req_spaces_before_url;
- } else if (ch == matcher[index]) {
+ if (ch == ' ' && matcher[parser->index] == '\0') {
+ parser->state = s_req_spaces_before_url;
+ } else if (ch == matcher[parser->index]) {
; /* nada */
} else if (parser->method == HTTP_CONNECT) {
- if (index == 1 && ch == 'H') {
+ if (parser->index == 1 && ch == 'H') {
parser->method = HTTP_CHECKOUT;
- } else if (index == 2 && ch == 'P') {
+ } else if (parser->index == 2 && ch == 'P') {
parser->method = HTTP_COPY;
} else {
goto error;
}
} else if (parser->method == HTTP_MKCOL) {
- if (index == 1 && ch == 'O') {
+ if (parser->index == 1 && ch == 'O') {
parser->method = HTTP_MOVE;
- } else if (index == 1 && ch == 'E') {
+ } else if (parser->index == 1 && ch == 'E') {
parser->method = HTTP_MERGE;
- } else if (index == 1 && ch == '-') {
+ } else if (parser->index == 1 && ch == '-') {
parser->method = HTTP_MSEARCH;
- } else if (index == 2 && ch == 'A') {
+ } else if (parser->index == 2 && ch == 'A') {
parser->method = HTTP_MKACTIVITY;
} else {
goto error;
}
- } else if (index == 1 && parser->method == HTTP_POST) {
+ } else if (parser->method == HTTP_SUBSCRIBE) {
+ if (parser->index == 1 && ch == 'E') {
+ parser->method = HTTP_SEARCH;
+ } else {
+ goto error;
+ }
+ } else if (parser->index == 1 && parser->method == HTTP_POST) {
if (ch == 'R') {
parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
} else if (ch == 'U') {
- parser->method = HTTP_PUT;
+ parser->method = HTTP_PUT; /* or HTTP_PURGE */
} else if (ch == 'A') {
parser->method = HTTP_PATCH;
} else {
goto error;
}
- } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') {
- parser->method = HTTP_UNSUBSCRIBE;
- } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
+ } else if (parser->index == 2) {
+ if (parser->method == HTTP_PUT) {
+ if (ch == 'R') parser->method = HTTP_PURGE;
+ } else if (parser->method == HTTP_UNLOCK) {
+ if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE;
+ }
+ } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
parser->method = HTTP_PROPPATCH;
} else {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
- ++index;
+ ++parser->index;
break;
}
+
case s_req_spaces_before_url:
{
if (ch == ' ') break;
- if (ch == '/' || ch == '*') {
- MARK(url);
- state = s_req_path;
- break;
+ MARK(url);
+ if (parser->method == HTTP_CONNECT) {
+ parser->state = s_req_server_start;
}
- /* Proxied requests are followed by scheme of an absolute URI (alpha).
- * CONNECT is followed by a hostname, which begins with alphanum.
- * All other methods are followed by '/' or '*' (handled above).
- */
- if (IS_ALPHA(ch) || (parser->method == HTTP_CONNECT && IS_NUM(ch))) {
- MARK(url);
- state = (parser->method == HTTP_CONNECT) ? s_req_host : s_req_schema;
- break;
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
}
- SET_ERRNO(HPE_INVALID_URL);
- goto error;
+ break;
}
case s_req_schema:
- {
- if (IS_ALPHA(ch)) break;
-
- if (ch == ':') {
- state = s_req_schema_slash;
- break;
- }
-
- SET_ERRNO(HPE_INVALID_URL);
- goto error;
- }
-
case s_req_schema_slash:
- STRICT_CHECK(ch != '/');
- state = s_req_schema_slash_slash;
- break;
-
case s_req_schema_slash_slash:
- STRICT_CHECK(ch != '/');
- state = s_req_host;
- break;
-
- case s_req_host:
- {
- if (IS_HOST_CHAR(ch)) break;
- switch (ch) {
- case ':':
- state = s_req_port;
- break;
- case '/':
- state = s_req_path;
- break;
- case ' ':
- /* The request line looks like:
- * "GET http://foo.bar.com HTTP/1.1"
- * That is, there is no path.
- */
- CALLBACK(url);
- state = s_req_http_start;
- break;
- case '?':
- state = s_req_query_string_start;
- break;
- default:
- SET_ERRNO(HPE_INVALID_HOST);
- goto error;
- }
- break;
- }
-
- case s_req_port:
- {
- if (IS_NUM(ch)) break;
- switch (ch) {
- case '/':
- state = s_req_path;
- break;
- case ' ':
- /* The request line looks like:
- * "GET http://foo.bar.com:1234 HTTP/1.1"
- * That is, there is no path.
- */
- CALLBACK(url);
- state = s_req_http_start;
- break;
- case '?':
- state = s_req_query_string_start;
- break;
- default:
- SET_ERRNO(HPE_INVALID_PORT);
- goto error;
- }
- break;
- }
-
- case s_req_path:
+ case s_req_server_start:
{
- if (IS_URL_CHAR(ch)) break;
-
switch (ch) {
+ /* No whitespace allowed here */
case ' ':
- CALLBACK(url);
- state = s_req_http_start;
- break;
case CR:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_req_line_almost_done;
- break;
case LF:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_header_field_start;
- break;
- case '?':
- state = s_req_query_string_start;
- break;
- case '#':
- state = s_req_fragment_start;
- break;
- default:
- SET_ERRNO(HPE_INVALID_PATH);
+ SET_ERRNO(HPE_INVALID_URL);
goto error;
- }
- break;
- }
-
- case s_req_query_string_start:
- {
- if (IS_URL_CHAR(ch)) {
- state = s_req_query_string;
- break;
- }
-
- switch (ch) {
- case '?':
- break; /* XXX ignore extra '?' ... is this right? */
- case ' ':
- CALLBACK(url);
- state = s_req_http_start;
- break;
- case CR:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_req_line_almost_done;
- break;
- case LF:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_header_field_start;
- break;
- case '#':
- state = s_req_fragment_start;
- break;
default:
- SET_ERRNO(HPE_INVALID_QUERY_STRING);
- goto error;
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
}
- break;
- }
- case s_req_query_string:
- {
- if (IS_URL_CHAR(ch)) break;
-
- switch (ch) {
- case '?':
- /* allow extra '?' in query string */
- break;
- case ' ':
- CALLBACK(url);
- state = s_req_http_start;
- break;
- case CR:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_req_line_almost_done;
- break;
- case LF:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_header_field_start;
- break;
- case '#':
- state = s_req_fragment_start;
- break;
- default:
- SET_ERRNO(HPE_INVALID_QUERY_STRING);
- goto error;
- }
break;
}
+ case s_req_server:
+ case s_req_server_with_at:
+ case s_req_path:
+ case s_req_query_string_start:
+ case s_req_query_string:
case s_req_fragment_start:
- {
- if (IS_URL_CHAR(ch)) {
- state = s_req_fragment;
- break;
- }
-
- switch (ch) {
- case ' ':
- CALLBACK(url);
- state = s_req_http_start;
- break;
- case CR:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_req_line_almost_done;
- break;
- case LF:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_header_field_start;
- break;
- case '?':
- state = s_req_fragment;
- break;
- case '#':
- break;
- default:
- SET_ERRNO(HPE_INVALID_FRAGMENT);
- goto error;
- }
- break;
- }
-
case s_req_fragment:
{
- if (IS_URL_CHAR(ch)) break;
-
switch (ch) {
case ' ':
- CALLBACK(url);
- state = s_req_http_start;
+ parser->state = s_req_http_start;
+ CALLBACK_DATA(url);
break;
case CR:
- CALLBACK(url);
- parser->http_major = 0;
- parser->http_minor = 9;
- state = s_req_line_almost_done;
- break;
case LF:
- CALLBACK(url);
parser->http_major = 0;
parser->http_minor = 9;
- state = s_header_field_start;
- break;
- case '?':
- case '#':
+ parser->state = (ch == CR) ?
+ s_req_line_almost_done :
+ s_header_field_start;
+ CALLBACK_DATA(url);
break;
default:
- SET_ERRNO(HPE_INVALID_FRAGMENT);
- goto error;
+ parser->state = parse_url_char((enum state)parser->state, ch);
+ if (parser->state == s_dead) {
+ SET_ERRNO(HPE_INVALID_URL);
+ goto error;
+ }
}
break;
}
@@ -1018,7 +1051,7 @@ size_t http_parser_execute (http_parser *parser,
case s_req_http_start:
switch (ch) {
case 'H':
- state = s_req_http_H;
+ parser->state = s_req_http_H;
break;
case ' ':
break;
@@ -1030,22 +1063,22 @@ size_t http_parser_execute (http_parser *parser,
case s_req_http_H:
STRICT_CHECK(ch != 'T');
- state = s_req_http_HT;
+ parser->state = s_req_http_HT;
break;
case s_req_http_HT:
STRICT_CHECK(ch != 'T');
- state = s_req_http_HTT;
+ parser->state = s_req_http_HTT;
break;
case s_req_http_HTT:
STRICT_CHECK(ch != 'P');
- state = s_req_http_HTTP;
+ parser->state = s_req_http_HTTP;
break;
case s_req_http_HTTP:
STRICT_CHECK(ch != '/');
- state = s_req_first_http_major;
+ parser->state = s_req_first_http_major;
break;
/* first digit of major HTTP version */
@@ -1056,14 +1089,14 @@ size_t http_parser_execute (http_parser *parser,
}
parser->http_major = ch - '0';
- state = s_req_http_major;
+ parser->state = s_req_http_major;
break;
/* major HTTP version or dot */
case s_req_http_major:
{
if (ch == '.') {
- state = s_req_first_http_minor;
+ parser->state = s_req_first_http_minor;
break;
}
@@ -1091,19 +1124,19 @@ size_t http_parser_execute (http_parser *parser,
}
parser->http_minor = ch - '0';
- state = s_req_http_minor;
+ parser->state = s_req_http_minor;
break;
/* minor HTTP version or end of request line */
case s_req_http_minor:
{
if (ch == CR) {
- state = s_req_line_almost_done;
+ parser->state = s_req_line_almost_done;
break;
}
if (ch == LF) {
- state = s_header_field_start;
+ parser->state = s_header_field_start;
break;
}
@@ -1133,23 +1166,22 @@ size_t http_parser_execute (http_parser *parser,
goto error;
}
- state = s_header_field_start;
+ parser->state = s_header_field_start;
break;
}
case s_header_field_start:
- header_field_start:
{
if (ch == CR) {
- state = s_headers_almost_done;
+ parser->state = s_headers_almost_done;
break;
}
if (ch == LF) {
/* they might be just sending \n instead of \r\n so this would be
* the second \n to denote the end of headers*/
- state = s_headers_almost_done;
- goto headers_almost_done;
+ parser->state = s_headers_almost_done;
+ goto reexecute_byte;
}
c = TOKEN(ch);
@@ -1161,28 +1193,28 @@ size_t http_parser_execute (http_parser *parser,
MARK(header_field);
- index = 0;
- state = s_header_field;
+ parser->index = 0;
+ parser->state = s_header_field;
switch (c) {
case 'c':
- header_state = h_C;
+ parser->header_state = h_C;
break;
case 'p':
- header_state = h_matching_proxy_connection;
+ parser->header_state = h_matching_proxy_connection;
break;
case 't':
- header_state = h_matching_transfer_encoding;
+ parser->header_state = h_matching_transfer_encoding;
break;
case 'u':
- header_state = h_matching_upgrade;
+ parser->header_state = h_matching_upgrade;
break;
default:
- header_state = h_general;
+ parser->header_state = h_general;
break;
}
break;
@@ -1193,31 +1225,31 @@ size_t http_parser_execute (http_parser *parser,
c = TOKEN(ch);
if (c) {
- switch (header_state) {
+ switch (parser->header_state) {
case h_general:
break;
case h_C:
- index++;
- header_state = (c == 'o' ? h_CO : h_general);
+ parser->index++;
+ parser->header_state = (c == 'o' ? h_CO : h_general);
break;
case h_CO:
- index++;
- header_state = (c == 'n' ? h_CON : h_general);
+ parser->index++;
+ parser->header_state = (c == 'n' ? h_CON : h_general);
break;
case h_CON:
- index++;
+ parser->index++;
switch (c) {
case 'n':
- header_state = h_matching_connection;
+ parser->header_state = h_matching_connection;
break;
case 't':
- header_state = h_matching_content_length;
+ parser->header_state = h_matching_content_length;
break;
default:
- header_state = h_general;
+ parser->header_state = h_general;
break;
}
break;
@@ -1225,60 +1257,60 @@ size_t http_parser_execute (http_parser *parser,
/* connection */
case h_matching_connection:
- index++;
- if (index > sizeof(CONNECTION)-1
- || c != CONNECTION[index]) {
- header_state = h_general;
- } else if (index == sizeof(CONNECTION)-2) {
- header_state = h_connection;
+ parser->index++;
+ if (parser->index > sizeof(CONNECTION)-1
+ || c != CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CONNECTION)-2) {
+ parser->header_state = h_connection;
}
break;
/* proxy-connection */
case h_matching_proxy_connection:
- index++;
- if (index > sizeof(PROXY_CONNECTION)-1
- || c != PROXY_CONNECTION[index]) {
- header_state = h_general;
- } else if (index == sizeof(PROXY_CONNECTION)-2) {
- header_state = h_connection;
+ parser->index++;
+ if (parser->index > sizeof(PROXY_CONNECTION)-1
+ || c != PROXY_CONNECTION[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(PROXY_CONNECTION)-2) {
+ parser->header_state = h_connection;
}
break;
/* content-length */
case h_matching_content_length:
- index++;
- if (index > sizeof(CONTENT_LENGTH)-1
- || c != CONTENT_LENGTH[index]) {
- header_state = h_general;
- } else if (index == sizeof(CONTENT_LENGTH)-2) {
- header_state = h_content_length;
+ parser->index++;
+ if (parser->index > sizeof(CONTENT_LENGTH)-1
+ || c != CONTENT_LENGTH[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
+ parser->header_state = h_content_length;
}
break;
/* transfer-encoding */
case h_matching_transfer_encoding:
- index++;
- if (index > sizeof(TRANSFER_ENCODING)-1
- || c != TRANSFER_ENCODING[index]) {
- header_state = h_general;
- } else if (index == sizeof(TRANSFER_ENCODING)-2) {
- header_state = h_transfer_encoding;
+ parser->index++;
+ if (parser->index > sizeof(TRANSFER_ENCODING)-1
+ || c != TRANSFER_ENCODING[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) {
+ parser->header_state = h_transfer_encoding;
}
break;
/* upgrade */
case h_matching_upgrade:
- index++;
- if (index > sizeof(UPGRADE)-1
- || c != UPGRADE[index]) {
- header_state = h_general;
- } else if (index == sizeof(UPGRADE)-2) {
- header_state = h_upgrade;
+ parser->index++;
+ if (parser->index > sizeof(UPGRADE)-1
+ || c != UPGRADE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(UPGRADE)-2) {
+ parser->header_state = h_upgrade;
}
break;
@@ -1286,7 +1318,7 @@ size_t http_parser_execute (http_parser *parser,
case h_content_length:
case h_transfer_encoding:
case h_upgrade:
- if (ch != ' ') header_state = h_general;
+ if (ch != ' ') parser->header_state = h_general;
break;
default:
@@ -1297,20 +1329,20 @@ size_t http_parser_execute (http_parser *parser,
}
if (ch == ':') {
- CALLBACK(header_field);
- state = s_header_value_start;
+ parser->state = s_header_value_start;
+ CALLBACK_DATA(header_field);
break;
}
if (ch == CR) {
- state = s_header_almost_done;
- CALLBACK(header_field);
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_field);
break;
}
if (ch == LF) {
- CALLBACK(header_field);
- state = s_header_field_start;
+ parser->state = s_header_field_start;
+ CALLBACK_DATA(header_field);
break;
}
@@ -1324,36 +1356,36 @@ size_t http_parser_execute (http_parser *parser,
MARK(header_value);
- state = s_header_value;
- index = 0;
+ parser->state = s_header_value;
+ parser->index = 0;
if (ch == CR) {
- CALLBACK(header_value);
- header_state = h_general;
- state = s_header_almost_done;
+ parser->header_state = h_general;
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_value);
break;
}
if (ch == LF) {
- CALLBACK(header_value);
- state = s_header_field_start;
+ parser->state = s_header_field_start;
+ CALLBACK_DATA(header_value);
break;
}
c = LOWER(ch);
- switch (header_state) {
+ switch (parser->header_state) {
case h_upgrade:
parser->flags |= F_UPGRADE;
- header_state = h_general;
+ parser->header_state = h_general;
break;
case h_transfer_encoding:
/* looking for 'Transfer-Encoding: chunked' */
if ('c' == c) {
- header_state = h_matching_transfer_encoding_chunked;
+ parser->header_state = h_matching_transfer_encoding_chunked;
} else {
- header_state = h_general;
+ parser->header_state = h_general;
}
break;
@@ -1369,17 +1401,17 @@ size_t http_parser_execute (http_parser *parser,
case h_connection:
/* looking for 'Connection: keep-alive' */
if (c == 'k') {
- header_state = h_matching_connection_keep_alive;
+ parser->header_state = h_matching_connection_keep_alive;
/* looking for 'Connection: close' */
} else if (c == 'c') {
- header_state = h_matching_connection_close;
+ parser->header_state = h_matching_connection_close;
} else {
- header_state = h_general;
+ parser->header_state = h_general;
}
break;
default:
- header_state = h_general;
+ parser->header_state = h_general;
break;
}
break;
@@ -1389,19 +1421,20 @@ size_t http_parser_execute (http_parser *parser,
{
if (ch == CR) {
- CALLBACK(header_value);
- state = s_header_almost_done;
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA(header_value);
break;
}
if (ch == LF) {
- CALLBACK(header_value);
- goto header_almost_done;
+ parser->state = s_header_almost_done;
+ CALLBACK_DATA_NOADVANCE(header_value);
+ goto reexecute_byte;
}
c = LOWER(ch);
- switch (header_state) {
+ switch (parser->header_state) {
case h_general:
break;
@@ -1411,70 +1444,83 @@ size_t http_parser_execute (http_parser *parser,
break;
case h_content_length:
+ {
+ uint64_t t;
+
if (ch == ' ') break;
+
if (!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto error;
}
- parser->content_length *= 10;
- parser->content_length += ch - '0';
+ t = parser->content_length;
+ t *= 10;
+ t += ch - '0';
+
+ /* Overflow? */
+ if (t < parser->content_length || t == ULLONG_MAX) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = t;
break;
+ }
/* Transfer-Encoding: chunked */
case h_matching_transfer_encoding_chunked:
- index++;
- if (index > sizeof(CHUNKED)-1
- || c != CHUNKED[index]) {
- header_state = h_general;
- } else if (index == sizeof(CHUNKED)-2) {
- header_state = h_transfer_encoding_chunked;
+ parser->index++;
+ if (parser->index > sizeof(CHUNKED)-1
+ || c != CHUNKED[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CHUNKED)-2) {
+ parser->header_state = h_transfer_encoding_chunked;
}
break;
/* looking for 'Connection: keep-alive' */
case h_matching_connection_keep_alive:
- index++;
- if (index > sizeof(KEEP_ALIVE)-1
- || c != KEEP_ALIVE[index]) {
- header_state = h_general;
- } else if (index == sizeof(KEEP_ALIVE)-2) {
- header_state = h_connection_keep_alive;
+ parser->index++;
+ if (parser->index > sizeof(KEEP_ALIVE)-1
+ || c != KEEP_ALIVE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(KEEP_ALIVE)-2) {
+ parser->header_state = h_connection_keep_alive;
}
break;
/* looking for 'Connection: close' */
case h_matching_connection_close:
- index++;
- if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) {
- header_state = h_general;
- } else if (index == sizeof(CLOSE)-2) {
- header_state = h_connection_close;
+ parser->index++;
+ if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
+ parser->header_state = h_general;
+ } else if (parser->index == sizeof(CLOSE)-2) {
+ parser->header_state = h_connection_close;
}
break;
case h_transfer_encoding_chunked:
case h_connection_keep_alive:
case h_connection_close:
- if (ch != ' ') header_state = h_general;
+ if (ch != ' ') parser->header_state = h_general;
break;
default:
- state = s_header_value;
- header_state = h_general;
+ parser->state = s_header_value;
+ parser->header_state = h_general;
break;
}
break;
}
case s_header_almost_done:
- header_almost_done:
{
STRICT_CHECK(ch != LF);
- state = s_header_value_lws;
+ parser->state = s_header_value_lws;
- switch (header_state) {
+ switch (parser->header_state) {
case h_connection_keep_alive:
parser->flags |= F_CONNECTION_KEEP_ALIVE;
break;
@@ -1487,44 +1533,47 @@ size_t http_parser_execute (http_parser *parser,
default:
break;
}
+
break;
}
case s_header_value_lws:
{
if (ch == ' ' || ch == '\t')
- state = s_header_value_start;
+ parser->state = s_header_value_start;
else
{
- state = s_header_field_start;
- goto header_field_start;
+ parser->state = s_header_field_start;
+ goto reexecute_byte;
}
break;
}
case s_headers_almost_done:
- headers_almost_done:
{
STRICT_CHECK(ch != LF);
if (parser->flags & F_TRAILING) {
/* End of a chunked request */
- CALLBACK2(message_complete);
- state = NEW_MESSAGE();
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
break;
}
- nread = 0;
+ parser->state = s_headers_done;
- if (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT) {
- parser->upgrade = 1;
- }
+ /* Set this here so that on_headers_complete() callbacks can see it */
+ parser->upgrade =
+ (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT);
/* Here we call the headers_complete callback. This is somewhat
* different than other callbacks because if the user returns 1, we
* will interpret that as saying that this message has no body. This
* is needed for the annoying case of recieving a response to a HEAD
* request.
+ *
+ * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
+ * we have to simulate it by handling a change in errno below.
*/
if (settings->on_headers_complete) {
switch (settings->on_headers_complete(parser)) {
@@ -1536,40 +1585,54 @@ size_t http_parser_execute (http_parser *parser,
break;
default:
- parser->state = state;
SET_ERRNO(HPE_CB_headers_complete);
return p - data; /* Error */
}
}
+ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
+ return p - data;
+ }
+
+ goto reexecute_byte;
+ }
+
+ case s_headers_done:
+ {
+ STRICT_CHECK(ch != LF);
+
+ parser->nread = 0;
+
/* Exit, the rest of the connect is in a different protocol. */
if (parser->upgrade) {
- CALLBACK2(message_complete);
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
return (p - data) + 1;
}
if (parser->flags & F_SKIPBODY) {
- CALLBACK2(message_complete);
- state = NEW_MESSAGE();
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
} else if (parser->flags & F_CHUNKED) {
/* chunked encoding - ignore Content-Length header */
- state = s_chunk_size_start;
+ parser->state = s_chunk_size_start;
} else {
if (parser->content_length == 0) {
/* Content-Length header given but zero: Content-Length: 0\r\n */
- CALLBACK2(message_complete);
- state = NEW_MESSAGE();
- } else if (parser->content_length > 0) {
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
+ } else if (parser->content_length != ULLONG_MAX) {
/* Content-Length header given and non-zero */
- state = s_body_identity;
+ parser->state = s_body_identity;
} else {
- if (parser->type == HTTP_REQUEST || http_should_keep_alive(parser)) {
+ if (parser->type == HTTP_REQUEST ||
+ !http_message_needs_eof(parser)) {
/* Assume content-length 0 - read the next */
- CALLBACK2(message_complete);
- state = NEW_MESSAGE();
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
} else {
/* Read body until EOF */
- state = s_body_identity_eof;
+ parser->state = s_body_identity_eof;
}
}
}
@@ -1578,30 +1641,56 @@ size_t http_parser_execute (http_parser *parser,
}
case s_body_identity:
- to_read = (size_t)MIN(pe - p, parser->content_length);
- if (to_read > 0) {
- if (settings->on_body) settings->on_body(parser, p, to_read);
- p += to_read - 1;
- parser->content_length -= to_read;
- if (parser->content_length == 0) {
- CALLBACK2(message_complete);
- state = NEW_MESSAGE();
- }
+ {
+ uint64_t to_read = MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
+
+ assert(parser->content_length != 0
+ && parser->content_length != ULLONG_MAX);
+
+ /* The difference between advancing content_length and p is because
+ * the latter will automaticaly advance on the next loop iteration.
+ * Further, if content_length ends up at 0, we want to see the last
+ * byte again for our message complete callback.
+ */
+ MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
+
+ if (parser->content_length == 0) {
+ parser->state = s_message_done;
+
+ /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
+ *
+ * The alternative to doing this is to wait for the next byte to
+ * trigger the data callback, just as in every other case. The
+ * problem with this is that this makes it difficult for the test
+ * harness to distinguish between complete-on-EOF and
+ * complete-on-length. It's not clear that this distinction is
+ * important for applications, but let's keep it for now.
+ */
+ CALLBACK_DATA_(body, p - body_mark + 1, p - data);
+ goto reexecute_byte;
}
+
break;
+ }
/* read until EOF */
case s_body_identity_eof:
- to_read = pe - p;
- if (to_read > 0) {
- if (settings->on_body) settings->on_body(parser, p, to_read);
- p += to_read - 1;
- }
+ MARK(body);
+ p = data + len - 1;
+
+ break;
+
+ case s_message_done:
+ parser->state = NEW_MESSAGE();
+ CALLBACK_NOTIFY(message_complete);
break;
case s_chunk_size_start:
{
- assert(nread == 1);
+ assert(parser->nread == 1);
assert(parser->flags & F_CHUNKED);
unhex_val = unhex[(unsigned char)ch];
@@ -1611,16 +1700,18 @@ size_t http_parser_execute (http_parser *parser,
}
parser->content_length = unhex_val;
- state = s_chunk_size;
+ parser->state = s_chunk_size;
break;
}
case s_chunk_size:
{
+ uint64_t t;
+
assert(parser->flags & F_CHUNKED);
if (ch == CR) {
- state = s_chunk_size_almost_done;
+ parser->state = s_chunk_size_almost_done;
break;
}
@@ -1628,7 +1719,7 @@ size_t http_parser_execute (http_parser *parser,
if (unhex_val == -1) {
if (ch == ';' || ch == ' ') {
- state = s_chunk_parameters;
+ parser->state = s_chunk_parameters;
break;
}
@@ -1636,8 +1727,17 @@ size_t http_parser_execute (http_parser *parser,
goto error;
}
- parser->content_length *= 16;
- parser->content_length += unhex_val;
+ t = parser->content_length;
+ t *= 16;
+ t += unhex_val;
+
+ /* Overflow? */
+ if (t < parser->content_length || t == ULLONG_MAX) {
+ SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
+ goto error;
+ }
+
+ parser->content_length = t;
break;
}
@@ -1646,7 +1746,7 @@ size_t http_parser_execute (http_parser *parser,
assert(parser->flags & F_CHUNKED);
/* just ignore this shit. TODO check for overflow */
if (ch == CR) {
- state = s_chunk_size_almost_done;
+ parser->state = s_chunk_size_almost_done;
break;
}
break;
@@ -1657,46 +1757,53 @@ size_t http_parser_execute (http_parser *parser,
assert(parser->flags & F_CHUNKED);
STRICT_CHECK(ch != LF);
- nread = 0;
+ parser->nread = 0;
if (parser->content_length == 0) {
parser->flags |= F_TRAILING;
- state = s_header_field_start;
+ parser->state = s_header_field_start;
} else {
- state = s_chunk_data;
+ parser->state = s_chunk_data;
}
break;
}
case s_chunk_data:
{
- assert(parser->flags & F_CHUNKED);
+ uint64_t to_read = MIN(parser->content_length,
+ (uint64_t) ((data + len) - p));
- to_read = (size_t)MIN(pe - p, parser->content_length);
+ assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length != 0
+ && parser->content_length != ULLONG_MAX);
- if (to_read > 0) {
- if (settings->on_body) settings->on_body(parser, p, to_read);
- p += to_read - 1;
- }
+ /* See the explanation in s_body_identity for why the content
+ * length and data pointers are managed this way.
+ */
+ MARK(body);
+ parser->content_length -= to_read;
+ p += to_read - 1;
- if ((signed)to_read == parser->content_length) {
- state = s_chunk_data_almost_done;
+ if (parser->content_length == 0) {
+ parser->state = s_chunk_data_almost_done;
}
- parser->content_length -= to_read;
break;
}
case s_chunk_data_almost_done:
assert(parser->flags & F_CHUNKED);
+ assert(parser->content_length == 0);
STRICT_CHECK(ch != CR);
- state = s_chunk_data_done;
+ parser->state = s_chunk_data_done;
+ CALLBACK_DATA(body);
break;
case s_chunk_data_done:
assert(parser->flags & F_CHUNKED);
STRICT_CHECK(ch != LF);
- state = s_chunk_size_start;
+ parser->nread = 0;
+ parser->state = s_chunk_size_start;
break;
default:
@@ -1706,14 +1813,25 @@ size_t http_parser_execute (http_parser *parser,
}
}
- CALLBACK(header_field);
- CALLBACK(header_value);
- CALLBACK(url);
+ /* Run callbacks for any marks that we have leftover after we ran our of
+ * bytes. There should be at most one of these set, so it's OK to invoke
+ * them in series (unset marks will not result in callbacks).
+ *
+ * We use the NOADVANCE() variety of callbacks here because 'p' has already
+ * overflowed 'data' and this allows us to correct for the off-by-one that
+ * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
+ * value that's in-bounds).
+ */
+
+ assert(((header_field_mark ? 1 : 0) +
+ (header_value_mark ? 1 : 0) +
+ (url_mark ? 1 : 0) +
+ (body_mark ? 1 : 0)) <= 1);
- parser->state = state;
- parser->header_state = header_state;
- parser->index = (unsigned char) index;
- parser->nread = nread;
+ CALLBACK_DATA_NOADVANCE(header_field);
+ CALLBACK_DATA_NOADVANCE(header_value);
+ CALLBACK_DATA_NOADVANCE(url);
+ CALLBACK_DATA_NOADVANCE(body);
return len;
@@ -1726,43 +1844,65 @@ error:
}
+/* Does the parser need to see an EOF to find the end of the message? */
int
-http_should_keep_alive (http_parser *parser)
+http_message_needs_eof (const http_parser *parser)
+{
+ if (parser->type == HTTP_REQUEST) {
+ return 0;
+ }
+
+ /* See RFC 2616 section 4.4 */
+ if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */
+ parser->status_code == 204 || /* No Content */
+ parser->status_code == 304 || /* Not Modified */
+ parser->flags & F_SKIPBODY) { /* response to a HEAD request */
+ return 0;
+ }
+
+ if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int
+http_should_keep_alive (const http_parser *parser)
{
if (parser->http_major > 0 && parser->http_minor > 0) {
/* HTTP/1.1 */
if (parser->flags & F_CONNECTION_CLOSE) {
return 0;
- } else {
- return 1;
}
} else {
/* HTTP/1.0 or earlier */
- if (parser->flags & F_CONNECTION_KEEP_ALIVE) {
- return 1;
- } else {
+ if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
return 0;
}
}
+
+ return !http_message_needs_eof(parser);
}
-const char * http_method_str (enum http_method m)
+const char *
+http_method_str (enum http_method m)
{
- return method_strings[m];
+ return ELEM_AT(method_strings, m, "<unknown>");
}
void
http_parser_init (http_parser *parser, enum http_parser_type t)
{
+ void *data = parser->data; /* preserve application data */
+ memset(parser, 0, sizeof(*parser));
+ parser->data = data;
parser->type = t;
parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
- parser->nread = 0;
- parser->upgrade = 0;
- parser->flags = 0;
- parser->method = 0;
- parser->http_errno = 0;
+ parser->http_errno = HPE_OK;
}
const char *
@@ -1776,3 +1916,259 @@ http_errno_description(enum http_errno err) {
assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0])));
return http_strerror_tab[err].description;
}
+
+static enum http_host_state
+http_parse_host_char(enum http_host_state s, const char ch) {
+ switch(s) {
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ if (ch == '@') {
+ return s_http_host_start;
+ }
+
+ if (IS_USERINFO_CHAR(ch)) {
+ return s_http_userinfo;
+ }
+ break;
+
+ case s_http_host_start:
+ if (ch == '[') {
+ return s_http_host_v6_start;
+ }
+
+ if (IS_HOST_CHAR(ch)) {
+ return s_http_host;
+ }
+
+ break;
+
+ case s_http_host:
+ if (IS_HOST_CHAR(ch)) {
+ return s_http_host;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_end:
+ if (ch == ':') {
+ return s_http_host_port_start;
+ }
+
+ break;
+
+ case s_http_host_v6:
+ if (ch == ']') {
+ return s_http_host_v6_end;
+ }
+
+ /* FALLTHROUGH */
+ case s_http_host_v6_start:
+ if (IS_HEX(ch) || ch == ':') {
+ return s_http_host_v6;
+ }
+
+ break;
+
+ case s_http_host_port:
+ case s_http_host_port_start:
+ if (IS_NUM(ch)) {
+ return s_http_host_port;
+ }
+
+ break;
+
+ default:
+ break;
+ }
+ return s_http_host_dead;
+}
+
+static int
+http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
+ enum http_host_state s;
+
+ const char *p;
+ size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
+
+ u->field_data[UF_HOST].len = 0;
+
+ s = found_at ? s_http_userinfo_start : s_http_host_start;
+
+ for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
+ enum http_host_state new_s = http_parse_host_char(s, *p);
+
+ if (new_s == s_http_host_dead) {
+ return 1;
+ }
+
+ switch(new_s) {
+ case s_http_host:
+ if (s != s_http_host) {
+ u->field_data[UF_HOST].off = p - buf;
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_v6:
+ if (s != s_http_host_v6) {
+ u->field_data[UF_HOST].off = p - buf;
+ }
+ u->field_data[UF_HOST].len++;
+ break;
+
+ case s_http_host_port:
+ if (s != s_http_host_port) {
+ u->field_data[UF_PORT].off = p - buf;
+ u->field_data[UF_PORT].len = 0;
+ u->field_set |= (1 << UF_PORT);
+ }
+ u->field_data[UF_PORT].len++;
+ break;
+
+ case s_http_userinfo:
+ if (s != s_http_userinfo) {
+ u->field_data[UF_USERINFO].off = p - buf ;
+ u->field_data[UF_USERINFO].len = 0;
+ u->field_set |= (1 << UF_USERINFO);
+ }
+ u->field_data[UF_USERINFO].len++;
+ break;
+
+ default:
+ break;
+ }
+ s = new_s;
+ }
+
+ /* Make sure we don't end somewhere unexpected */
+ switch (s) {
+ case s_http_host_start:
+ case s_http_host_v6_start:
+ case s_http_host_v6:
+ case s_http_host_port_start:
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+int
+http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
+ struct http_parser_url *u)
+{
+ enum state s;
+ const char *p;
+ enum http_parser_url_fields uf, old_uf;
+ int found_at = 0;
+
+ u->port = u->field_set = 0;
+ s = is_connect ? s_req_server_start : s_req_spaces_before_url;
+ uf = old_uf = UF_MAX;
+
+ for (p = buf; p < buf + buflen; p++) {
+ s = parse_url_char(s, *p);
+
+ /* Figure out the next field that we're operating on */
+ switch (s) {
+ case s_dead:
+ return 1;
+
+ /* Skip delimeters */
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_query_string_start:
+ case s_req_fragment_start:
+ continue;
+
+ case s_req_schema:
+ uf = UF_SCHEMA;
+ break;
+
+ case s_req_server_with_at:
+ found_at = 1;
+
+ /* FALLTROUGH */
+ case s_req_server:
+ uf = UF_HOST;
+ break;
+
+ case s_req_path:
+ uf = UF_PATH;
+ break;
+
+ case s_req_query_string:
+ uf = UF_QUERY;
+ break;
+
+ case s_req_fragment:
+ uf = UF_FRAGMENT;
+ break;
+
+ default:
+ assert(!"Unexpected state");
+ return 1;
+ }
+
+ /* Nothing's changed; soldier on */
+ if (uf == old_uf) {
+ u->field_data[uf].len++;
+ continue;
+ }
+
+ u->field_data[uf].off = p - buf;
+ u->field_data[uf].len = 1;
+
+ u->field_set |= (1 << uf);
+ old_uf = uf;
+ }
+
+ /* host must be present if there is a schema */
+ /* parsing http:///toto will fail */
+ if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {
+ if (http_parse_host(buf, u, found_at) != 0) {
+ return 1;
+ }
+ }
+
+ /* CONNECT requests can only contain "hostname:port" */
+ if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
+ return 1;
+ }
+
+ if (u->field_set & (1 << UF_PORT)) {
+ /* Don't bother with endp; we've already validated the string */
+ unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
+
+ /* Ports have a max value of 2^16 */
+ if (v > 0xffff) {
+ return 1;
+ }
+
+ u->port = (uint16_t) v;
+ }
+
+ return 0;
+}
+
+void
+http_parser_pause(http_parser *parser, int paused) {
+ /* Users should only be pausing/unpausing a parser that is not in an error
+ * state. In non-debug builds, there's not much that we can do about this
+ * other than ignore it.
+ */
+ if (HTTP_PARSER_ERRNO(parser) == HPE_OK ||
+ HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
+ SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
+ } else {
+ assert(0 && "Attempting to pause parser in error state");
+ }
+}
+
+int
+http_body_is_final(const struct http_parser *parser) {
+ return parser->state == s_message_done;
+}
diff --git a/deps/http-parser/http_parser.h b/deps/http-parser/http_parser.h
index b6f6e9978..4f20396c6 100644
--- a/deps/http-parser/http_parser.h
+++ b/deps/http-parser/http_parser.h
@@ -24,16 +24,25 @@
extern "C" {
#endif
-#define HTTP_PARSER_VERSION_MAJOR 1
+#define HTTP_PARSER_VERSION_MAJOR 2
#define HTTP_PARSER_VERSION_MINOR 0
-#ifdef _MSC_VER
- /* disable silly warnings */
-# pragma warning(disable: 4127 4214)
-#endif
-
#include <sys/types.h>
-#include "git2/common.h"
+#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
+#include <BaseTsd.h>
+typedef __int8 int8_t;
+typedef unsigned __int8 uint8_t;
+typedef __int16 int16_t;
+typedef unsigned __int16 uint16_t;
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef __int64 int64_t;
+typedef unsigned __int64 uint64_t;
+typedef SIZE_T size_t;
+typedef SSIZE_T ssize_t;
+#else
+#include <stdint.h>
+#endif
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
* faster
@@ -42,21 +51,12 @@ extern "C" {
# define HTTP_PARSER_STRICT 1
#endif
-/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to
- * the error reporting facility.
- */
-#ifndef HTTP_PARSER_DEBUG
-# define HTTP_PARSER_DEBUG 0
-#endif
-
-
/* Maximium header size allowed */
#define HTTP_MAX_HEADER_SIZE (80*1024)
typedef struct http_parser http_parser;
typedef struct http_parser_settings http_parser_settings;
-typedef struct http_parser_result http_parser_result;
/* Callbacks should return non-zero to indicate an error. The parser will
@@ -69,7 +69,7 @@ typedef struct http_parser_result http_parser_result;
* chunked' headers that indicate the presence of a body.
*
* http_data_cb does not return data chunks. It will be call arbitrarally
- * many times for each string. E.G. you might get 10 callbacks for "on_path"
+ * many times for each string. E.G. you might get 10 callbacks for "on_url"
* each providing just a few characters more data.
*/
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
@@ -77,36 +77,44 @@ typedef int (*http_cb) (http_parser*);
/* Request Methods */
+#define HTTP_METHOD_MAP(XX) \
+ XX(0, DELETE, DELETE) \
+ XX(1, GET, GET) \
+ XX(2, HEAD, HEAD) \
+ XX(3, POST, POST) \
+ XX(4, PUT, PUT) \
+ /* pathological */ \
+ XX(5, CONNECT, CONNECT) \
+ XX(6, OPTIONS, OPTIONS) \
+ XX(7, TRACE, TRACE) \
+ /* webdav */ \
+ XX(8, COPY, COPY) \
+ XX(9, LOCK, LOCK) \
+ XX(10, MKCOL, MKCOL) \
+ XX(11, MOVE, MOVE) \
+ XX(12, PROPFIND, PROPFIND) \
+ XX(13, PROPPATCH, PROPPATCH) \
+ XX(14, SEARCH, SEARCH) \
+ XX(15, UNLOCK, UNLOCK) \
+ /* subversion */ \
+ XX(16, REPORT, REPORT) \
+ XX(17, MKACTIVITY, MKACTIVITY) \
+ XX(18, CHECKOUT, CHECKOUT) \
+ XX(19, MERGE, MERGE) \
+ /* upnp */ \
+ XX(20, MSEARCH, M-SEARCH) \
+ XX(21, NOTIFY, NOTIFY) \
+ XX(22, SUBSCRIBE, SUBSCRIBE) \
+ XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \
+ /* RFC-5789 */ \
+ XX(24, PATCH, PATCH) \
+ XX(25, PURGE, PURGE) \
+
enum http_method
- { HTTP_DELETE = 0
- , HTTP_GET
- , HTTP_HEAD
- , HTTP_POST
- , HTTP_PUT
- /* pathological */
- , HTTP_CONNECT
- , HTTP_OPTIONS
- , HTTP_TRACE
- /* webdav */
- , HTTP_COPY
- , HTTP_LOCK
- , HTTP_MKCOL
- , HTTP_MOVE
- , HTTP_PROPFIND
- , HTTP_PROPPATCH
- , HTTP_UNLOCK
- /* subversion */
- , HTTP_REPORT
- , HTTP_MKACTIVITY
- , HTTP_CHECKOUT
- , HTTP_MERGE
- /* upnp */
- , HTTP_MSEARCH
- , HTTP_NOTIFY
- , HTTP_SUBSCRIBE
- , HTTP_UNSUBSCRIBE
- /* RFC-5789 */
- , HTTP_PATCH
+ {
+#define XX(num, name, string) HTTP_##name = num,
+ HTTP_METHOD_MAP(XX)
+#undef XX
};
@@ -134,10 +142,7 @@ enum flags
\
/* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \
- XX(CB_path, "the on_path callback failed") \
- XX(CB_query_string, "the on_query_string callback failed") \
XX(CB_url, "the on_url callback failed") \
- XX(CB_fragment, "the on_fragment callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \
@@ -168,6 +173,7 @@ enum flags
XX(INVALID_CONSTANT, "invalid constant string") \
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
XX(STRICT, "strict mode assertion failed") \
+ XX(PAUSED, "parser is paused") \
XX(UNKNOWN, "an unknown error occurred")
@@ -182,30 +188,23 @@ enum http_errno {
/* Get an http_errno value from an http_parser */
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
-/* Get the line number that generated the current error */
-#if HTTP_PARSER_DEBUG
-#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno)
-#else
-#define HTTP_PARSER_ERRNO_LINE(p) 0
-#endif
-
struct http_parser {
/** PRIVATE **/
- unsigned char type : 2;
- unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */
- unsigned char state;
- unsigned char header_state;
- unsigned char index;
+ unsigned char type : 2; /* enum http_parser_type */
+ unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */
+ unsigned char state; /* enum state from http_parser.c */
+ unsigned char header_state; /* enum header_state from http_parser.c */
+ unsigned char index; /* index into current matcher */
- size_t nread;
- int64_t content_length;
+ uint32_t nread; /* # bytes read in various scenarios */
+ uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
/** READ-ONLY **/
unsigned short http_major;
unsigned short http_minor;
unsigned short status_code; /* responses only */
- unsigned char method; /* requests only */
+ unsigned char method; /* requests only */
unsigned char http_errno : 7;
/* 1 = Upgrade header was present and the parser has exited because of that.
@@ -215,10 +214,6 @@ struct http_parser {
*/
unsigned char upgrade : 1;
-#if HTTP_PARSER_DEBUG
- uint32_t error_lineno;
-#endif
-
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
};
@@ -235,6 +230,36 @@ struct http_parser_settings {
};
+enum http_parser_url_fields
+ { UF_SCHEMA = 0
+ , UF_HOST = 1
+ , UF_PORT = 2
+ , UF_PATH = 3
+ , UF_QUERY = 4
+ , UF_FRAGMENT = 5
+ , UF_USERINFO = 6
+ , UF_MAX = 7
+ };
+
+
+/* Result structure for http_parser_parse_url().
+ *
+ * Callers should index into field_data[] with UF_* values iff field_set
+ * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
+ * because we probably have padding left over), we convert any port to
+ * a uint16_t.
+ */
+struct http_parser_url {
+ uint16_t field_set; /* Bitmask of (1 << UF_*) values */
+ uint16_t port; /* Converted UF_PORT string */
+
+ struct {
+ uint16_t off; /* Offset into buffer in which field starts */
+ uint16_t len; /* Length of run in buffer */
+ } field_data[UF_MAX];
+};
+
+
void http_parser_init(http_parser *parser, enum http_parser_type type);
@@ -245,12 +270,12 @@ size_t http_parser_execute(http_parser *parser,
/* If http_should_keep_alive() in the on_headers_complete or
- * on_message_complete callback returns true, then this will be should be
+ * on_message_complete callback returns 0, then this should be
* the last message on the connection.
* If you are the server, respond with the "Connection: close" header.
* If you are the client, close the connection.
*/
-int http_should_keep_alive(http_parser *parser);
+int http_should_keep_alive(const http_parser *parser);
/* Returns a string version of the HTTP method. */
const char *http_method_str(enum http_method m);
@@ -261,6 +286,17 @@ const char *http_errno_name(enum http_errno err);
/* Return a string description of the given error */
const char *http_errno_description(enum http_errno err);
+/* Parse a URL; return nonzero on failure */
+int http_parser_parse_url(const char *buf, size_t buflen,
+ int is_connect,
+ struct http_parser_url *u);
+
+/* Pause or un-pause the parser; a nonzero value pauses */
+void http_parser_pause(http_parser *parser, int paused);
+
+/* Checks if this is the final chunk of the body. */
+int http_body_is_final(const http_parser *parser);
+
#ifdef __cplusplus
}
#endif
diff --git a/examples/Makefile b/examples/Makefile
index fe99c75cb..da4df5240 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -1,7 +1,7 @@
.PHONY: all
CC = gcc
-CFLAGS = -g -I../include -I../src
+CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes
LFLAGS = -L../build -lgit2 -lz
APPS = general showindex diff
diff --git a/examples/diff.c b/examples/diff.c
index b72a75e1c..38231d219 100644
--- a/examples/diff.c
+++ b/examples/diff.c
@@ -3,7 +3,7 @@
#include <stdlib.h>
#include <string.h>
-void check(int error, const char *message)
+static void check(int error, const char *message)
{
if (error) {
fprintf(stderr, "%s (%d)\n", message, error);
@@ -11,7 +11,8 @@ void check(int error, const char *message)
}
}
-int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree)
+static int resolve_to_tree(
+ git_repository *repo, const char *identifier, git_tree **tree)
{
int err = 0;
size_t len = strlen(identifier);
@@ -61,16 +62,18 @@ char *colors[] = {
"\033[36m" /* cyan */
};
-int printer(
+static int printer(
void *data,
- git_diff_delta *delta,
- git_diff_range *range,
+ const git_diff_delta *delta,
+ const git_diff_range *range,
char usage,
const char *line,
size_t line_len)
{
int *last_color = data, color = 0;
+ (void)delta; (void)range; (void)line_len;
+
if (*last_color >= 0) {
switch (usage) {
case GIT_DIFF_LINE_ADDITION: color = 3; break;
@@ -93,7 +96,7 @@ int printer(
return 0;
}
-int check_uint16_param(const char *arg, const char *pattern, uint16_t *val)
+static int check_uint16_param(const char *arg, const char *pattern, uint16_t *val)
{
size_t len = strlen(pattern);
uint16_t strval;
@@ -107,7 +110,7 @@ int check_uint16_param(const char *arg, const char *pattern, uint16_t *val)
return 1;
}
-int check_str_param(const char *arg, const char *pattern, char **val)
+static int check_str_param(const char *arg, const char *pattern, char **val)
{
size_t len = strlen(pattern);
if (strncmp(arg, pattern, len))
@@ -116,7 +119,7 @@ int check_str_param(const char *arg, const char *pattern, char **val)
return 1;
}
-void usage(const char *message, const char *arg)
+static void usage(const char *message, const char *arg)
{
if (message && arg)
fprintf(stderr, "%s: %s\n", message, arg);
@@ -128,14 +131,15 @@ void usage(const char *message, const char *arg)
int main(int argc, char *argv[])
{
- char path[GIT_PATH_MAX];
git_repository *repo = NULL;
git_tree *t1 = NULL, *t2 = NULL;
- git_diff_options opts = {0};
+ git_diff_options opts;
git_diff_list *diff;
int i, color = -1, compact = 0, cached = 0;
char *a, *dir = ".", *treeish1 = NULL, *treeish2 = NULL;
+ memset(&opts, 0, sizeof(opts));
+
/* parse arguments as copied from git-diff */
for (i = 1; i < argc; ++i) {
@@ -200,22 +204,22 @@ int main(int argc, char *argv[])
/* nothing */
if (t1 && t2)
- check(git_diff_tree_to_tree(repo, &opts, t1, t2, &diff), "Diff");
+ check(git_diff_tree_to_tree(&diff, repo, t1, t2, &opts), "Diff");
else if (t1 && cached)
- check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff");
+ check(git_diff_index_to_tree(&diff, repo, t1, NULL, &opts), "Diff");
else if (t1) {
git_diff_list *diff2;
- check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff");
- check(git_diff_workdir_to_index(repo, &opts, &diff2), "Diff");
+ check(git_diff_index_to_tree(&diff, repo, t1, NULL, &opts), "Diff");
+ check(git_diff_workdir_to_index(&diff2, repo, NULL, &opts), "Diff");
check(git_diff_merge(diff, diff2), "Merge diffs");
git_diff_list_free(diff2);
}
else if (cached) {
check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD");
- check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff");
+ check(git_diff_index_to_tree(&diff, repo, t1, NULL, &opts), "Diff");
}
else
- check(git_diff_workdir_to_index(repo, &opts, &diff), "Diff");
+ check(git_diff_workdir_to_index(&diff, repo, NULL, &opts), "Diff");
if (color >= 0)
fputs(colors[0], stdout);
diff --git a/examples/general.c b/examples/general.c
index e001a6889..9ccb4c56e 100644
--- a/examples/general.c
+++ b/examples/general.c
@@ -239,7 +239,7 @@ int main (int argc, char** argv)
// the tagger (a git_signature - name, email, timestamp), and the tag message.
git_tag_target((git_object **)&commit, tag);
tname = git_tag_name(tag); // "test"
- ttype = git_tag_type(tag); // GIT_OBJ_COMMIT (otype enum)
+ ttype = git_tag_target_type(tag); // GIT_OBJ_COMMIT (otype enum)
tmessage = git_tag_message(tag); // "tag message\n"
printf("Tag Message: %s\n", tmessage);
@@ -371,7 +371,7 @@ int main (int argc, char** argv)
// All these properties are exported publicly in the `git_index_entry` struct
ecount = git_index_entrycount(index);
for (i = 0; i < ecount; ++i) {
- git_index_entry *e = git_index_get(index, i);
+ git_index_entry *e = git_index_get_byindex(index, i);
printf("path: %s\n", e->path);
printf("mtime: %d\n", (int)e->mtime.seconds);
diff --git a/examples/network/Makefile b/examples/network/Makefile
index 835be24cc..ef3cec659 100644
--- a/examples/network/Makefile
+++ b/examples/network/Makefile
@@ -2,7 +2,8 @@ default: all
CC = gcc
CFLAGS += -g
-CFLAGS += -I../../include -L../../build -L../.. -lgit2 -lpthread
+CFLAGS += -I../../include
+LDFLAGS += -L../../build -L../.. -lgit2 -lpthread
OBJECTS = \
git2.o \
@@ -12,4 +13,4 @@ OBJECTS = \
index-pack.o
all: $(OBJECTS)
- $(CC) $(CFLAGS) -o git2 $(OBJECTS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS)
diff --git a/examples/network/clone.c b/examples/network/clone.c
index fb571bd3a..30a4944c2 100644
--- a/examples/network/clone.c
+++ b/examples/network/clone.c
@@ -7,62 +7,78 @@
#include <pthread.h>
#include <unistd.h>
-struct dl_data {
- git_indexer_stats fetch_stats;
- git_indexer_stats checkout_stats;
- git_checkout_opts opts;
- int ret;
- int finished;
- const char *url;
+typedef struct progress_data {
+ git_transfer_progress fetch_progress;
+ size_t completed_steps;
+ size_t total_steps;
const char *path;
-};
+} progress_data;
-static void *clone_thread(void *ptr)
+static void print_progress(const progress_data *pd)
{
- struct dl_data *data = (struct dl_data *)ptr;
- git_repository *repo = NULL;
+ int network_percent = (100*pd->fetch_progress.received_objects) / pd->fetch_progress.total_objects;
+ int index_percent = (100*pd->fetch_progress.indexed_objects) / pd->fetch_progress.total_objects;
+ int checkout_percent = pd->total_steps > 0
+ ? (100 * pd->completed_steps) / pd->total_steps
+ : 0.f;
+ int kbytes = pd->fetch_progress.received_bytes / 1024;
- // Kick off the clone
- data->ret = git_clone(&repo, data->url, data->path,
- &data->fetch_stats, &data->checkout_stats,
- &data->opts);
- if (repo) git_repository_free(repo);
- data->finished = 1;
+ printf("net %3d%% (%4d kb, %5d/%5d) / idx %3d%% (%5d/%5d) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ ") %s\n",
+ network_percent, kbytes,
+ pd->fetch_progress.received_objects, pd->fetch_progress.total_objects,
+ index_percent, pd->fetch_progress.indexed_objects, pd->fetch_progress.total_objects,
+ checkout_percent,
+ pd->completed_steps, pd->total_steps,
+ pd->path);
+}
- pthread_exit(&data->ret);
+static void fetch_progress(const git_transfer_progress *stats, void *payload)
+{
+ progress_data *pd = (progress_data*)payload;
+ pd->fetch_progress = *stats;
+ print_progress(pd);
+}
+static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload)
+{
+ progress_data *pd = (progress_data*)payload;
+ pd->completed_steps = cur;
+ pd->total_steps = tot;
+ pd->path = path;
+ print_progress(pd);
}
int do_clone(git_repository *repo, int argc, char **argv)
{
- struct dl_data data = {0};
- pthread_t worker;
+ progress_data pd;
+ git_repository *cloned_repo = NULL;
+ git_checkout_opts checkout_opts;
+ const char *url = argv[1];
+ const char *path = argv[2];
+ int error;
+
+ (void)repo; // unused
// Validate args
if (argc < 3) {
- printf("USAGE: %s <url> <path>\n", argv[0]);
+ printf ("USAGE: %s <url> <path>\n", argv[0]);
return -1;
}
- // Data for background thread
- data.url = argv[1];
- data.path = argv[2];
- data.opts.disable_filters = 1;
- printf("Cloning '%s' to '%s'\n", data.url, data.path);
+ // Set up options
+ memset(&checkout_opts, 0, sizeof(checkout_opts));
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+ checkout_opts.progress_cb = checkout_progress;
+ memset(&pd, 0, sizeof(pd));
+ checkout_opts.progress_payload = &pd;
- // Create the worker thread
- pthread_create(&worker, NULL, clone_thread, &data);
-
- // Watch for progress information
- do {
- usleep(10000);
- printf("Fetch %d/%d – Checkout %d/%d\n",
- data.fetch_stats.processed, data.fetch_stats.total,
- data.checkout_stats.processed, data.checkout_stats.total);
- } while (!data.finished);
- printf("Fetch %d/%d – Checkout %d/%d\n",
- data.fetch_stats.processed, data.fetch_stats.total,
- data.checkout_stats.processed, data.checkout_stats.total);
-
- return data.ret;
+ // Do the clone
+ error = git_clone(&cloned_repo, url, path, &fetch_progress, &pd, &checkout_opts);
+ printf("\n");
+ if (error != 0) {
+ const git_error *err = giterr_last();
+ if (err) printf("ERROR %d: %s\n", err->klass, err->message);
+ else printf("ERROR %d: no detailed info\n", error);
+ }
+ else if (cloned_repo) git_repository_free(cloned_repo);
+ return error;
}
-
diff --git a/examples/network/common.h b/examples/network/common.h
index c82eaa1c8..a4cfa1a7e 100644
--- a/examples/network/common.h
+++ b/examples/network/common.h
@@ -12,4 +12,13 @@ int fetch(git_repository *repo, int argc, char **argv);
int index_pack(git_repository *repo, int argc, char **argv);
int do_clone(git_repository *repo, int argc, char **argv);
+#ifndef PRIuZ
+/* Define the printf format specifer to use for size_t output */
+#if defined(_MSC_VER) || defined(__MINGW32__)
+# define PRIuZ "Iu"
+#else
+# define PRIuZ "zu"
+#endif
+#endif
+
#endif /* __COMMON_H__ */
diff --git a/examples/network/fetch.c b/examples/network/fetch.c
index fa941b97a..9d1404ab4 100644
--- a/examples/network/fetch.c
+++ b/examples/network/fetch.c
@@ -8,8 +8,6 @@
struct dl_data {
git_remote *remote;
- git_off_t *bytes;
- git_indexer_stats *stats;
int ret;
int finished;
};
@@ -35,7 +33,7 @@ static void *download(void *ptr)
// Download the packfile and index it. This function updates the
// amount of received data and the indexer stats which lets you
// inform the user about progress.
- if (git_remote_download(data->remote, data->bytes, data->stats) < 0) {
+ if (git_remote_download(data->remote, NULL, NULL) < 0) {
data->ret = -1;
goto exit;
}
@@ -69,15 +67,14 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo
int fetch(git_repository *repo, int argc, char **argv)
{
git_remote *remote = NULL;
- git_off_t bytes = 0;
- git_indexer_stats stats;
+ const git_transfer_progress *stats;
pthread_t worker;
struct dl_data data;
git_remote_callbacks callbacks;
argc = argc;
// Figure out whether it's a named remote or a URL
- printf("Fetching %s\n", argv[1]);
+ printf("Fetching %s for repo %p\n", argv[1], repo);
if (git_remote_load(&remote, repo, argv[1]) < 0) {
if (git_remote_new(&remote, repo, NULL, argv[1], NULL) < 0)
return -1;
@@ -91,11 +88,10 @@ int fetch(git_repository *repo, int argc, char **argv)
// Set up the information for the background worker thread
data.remote = remote;
- data.bytes = &bytes;
- data.stats = &stats;
data.ret = 0;
data.finished = 0;
- memset(&stats, 0, sizeof(stats));
+
+ stats = git_remote_stats(remote);
pthread_create(&worker, NULL, download, &data);
@@ -106,16 +102,18 @@ int fetch(git_repository *repo, int argc, char **argv)
do {
usleep(10000);
- if (stats.total > 0)
- printf("Received %d/%d objects (%d) in %d bytes\r",
- stats.received, stats.total, stats.processed, bytes);
+ if (stats->total_objects > 0)
+ printf("Received %d/%d objects (%d) in %" PRIuZ " bytes\r",
+ stats->received_objects, stats->total_objects,
+ stats->indexed_objects, stats->received_bytes);
} while (!data.finished);
if (data.ret < 0)
goto on_error;
pthread_join(worker, NULL);
- printf("\rReceived %d/%d objects in %zu bytes\n", stats.processed, stats.total, bytes);
+ printf("\rReceived %d/%d objects in %zu bytes\n",
+ stats->indexed_objects, stats->total_objects, stats->received_bytes);
// Disconnect the underlying connection to prevent from idling.
git_remote_disconnect(remote);
diff --git a/examples/network/index-pack.c b/examples/network/index-pack.c
index 85aac4aff..4d3dc84d6 100644
--- a/examples/network/index-pack.c
+++ b/examples/network/index-pack.c
@@ -10,10 +10,10 @@
// This could be run in the main loop whilst the application waits for
// the indexing to finish in a worker thread
-static int index_cb(const git_indexer_stats *stats, void *data)
+static int index_cb(const git_transfer_progress *stats, void *data)
{
data = data;
- printf("\rProcessing %d of %d", stats->processed, stats->total);
+ printf("\rProcessing %d of %d", stats->indexed_objects, stats->total_objects);
return 0;
}
@@ -21,7 +21,7 @@ static int index_cb(const git_indexer_stats *stats, void *data)
int index_pack(git_repository *repo, int argc, char **argv)
{
git_indexer_stream *idx;
- git_indexer_stats stats = {0, 0};
+ git_transfer_progress stats = {0, 0};
int error, fd;
char hash[GIT_OID_HEXSZ + 1] = {0};
ssize_t read_bytes;
@@ -33,7 +33,7 @@ int index_pack(git_repository *repo, int argc, char **argv)
return EXIT_FAILURE;
}
- if (git_indexer_stream_new(&idx, ".") < 0) {
+ if (git_indexer_stream_new(&idx, ".", NULL, NULL) < 0) {
puts("bad idx");
return -1;
}
@@ -63,7 +63,7 @@ int index_pack(git_repository *repo, int argc, char **argv)
if ((error = git_indexer_stream_finalize(idx, &stats)) < 0)
goto cleanup;
- printf("\rIndexing %d of %d\n", stats.processed, stats.total);
+ printf("\rIndexing %d of %d\n", stats.indexed_objects, stats.total_objects);
git_oid_fmt(hash, git_indexer_stream_hash(idx));
puts(hash);
diff --git a/examples/showindex.c b/examples/showindex.c
index 7f2130b90..4b50ffd0f 100644
--- a/examples/showindex.c
+++ b/examples/showindex.c
@@ -3,41 +3,53 @@
int main (int argc, char** argv)
{
- git_repository *repo;
- git_index *index;
- unsigned int i, e, ecount;
- git_index_entry **entries;
- git_oid oid;
-
- char out[41];
- out[40] = '\0';
-
- git_repository_open(&repo, "/opt/libgit2-test/.git");
-
- git_repository_index(&index, repo);
- git_index_read(index);
-
- ecount = git_index_entrycount(index);
- for (i = 0; i < ecount; ++i) {
- git_index_entry *e = git_index_get(index, i);
-
- oid = e->oid;
- git_oid_fmt(out, &oid);
-
- printf("File Path: %s\n", e->path);
- printf(" Blob SHA: %s\n", out);
- printf("File Size: %d\n", (int)e->file_size);
- printf(" Device: %d\n", (int)e->dev);
- printf(" Inode: %d\n", (int)e->ino);
- printf(" UID: %d\n", (int)e->uid);
- printf(" GID: %d\n", (int)e->gid);
- printf(" ctime: %d\n", (int)e->ctime.seconds);
- printf(" mtime: %d\n", (int)e->mtime.seconds);
- printf("\n");
- }
-
- git_index_free(index);
-
- git_repository_free(repo);
+ git_repository *repo;
+ git_index *index;
+ unsigned int i, ecount;
+ char *dir = ".";
+ char out[41];
+ out[40] = '\0';
+
+ if (argc > 1)
+ dir = argv[1];
+ if (argc > 2) {
+ fprintf(stderr, "usage: showindex [<repo-dir>]\n");
+ return 1;
+ }
+
+ if (git_repository_open_ext(&repo, dir, 0, NULL) < 0) {
+ fprintf(stderr, "could not open repository: %s\n", dir);
+ return 1;
+ }
+
+ git_repository_index(&index, repo);
+ git_index_read(index);
+
+ ecount = git_index_entrycount(index);
+ if (!ecount)
+ printf("Empty index\n");
+
+ for (i = 0; i < ecount; ++i) {
+ const git_index_entry *e = git_index_get_byindex(index, i);
+
+ git_oid_fmt(out, &e->oid);
+
+ printf("File Path: %s\n", e->path);
+ printf(" Stage: %d\n", git_index_entry_stage(e));
+ printf(" Blob SHA: %s\n", out);
+ printf("File Size: %d\n", (int)e->file_size);
+ printf(" Device: %d\n", (int)e->dev);
+ printf(" Inode: %d\n", (int)e->ino);
+ printf(" UID: %d\n", (int)e->uid);
+ printf(" GID: %d\n", (int)e->gid);
+ printf(" ctime: %d\n", (int)e->ctime.seconds);
+ printf(" mtime: %d\n", (int)e->mtime.seconds);
+ printf("\n");
+ }
+
+ git_index_free(index);
+ git_repository_free(repo);
+
+ return 0;
}
diff --git a/include/git2.h b/include/git2.h
index d55543986..501128c43 100644
--- a/include/git2.h
+++ b/include/git2.h
@@ -36,6 +36,7 @@
#include "git2/index.h"
#include "git2/config.h"
+#include "git2/transport.h"
#include "git2/remote.h"
#include "git2/clone.h"
#include "git2/checkout.h"
@@ -52,5 +53,6 @@
#include "git2/reset.h"
#include "git2/message.h"
#include "git2/pack.h"
+#include "git2/stash.h"
#endif
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index b4f9ad081..27ecc7102 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -25,20 +25,121 @@ GIT_BEGIN_DECL
* Checkout behavior flags
*
* These flags control what checkout does with files. Pass in a
- * combination of these values OR'ed together.
+ * combination of these values OR'ed together. If you just pass zero
+ * (i.e. no flags), then you are effectively doing a "dry run" where no
+ * files will be modified.
+ *
+ * Checkout groups the working directory content into 3 classes of files:
+ * (1) files that don't need a change, and files that do need a change
+ * that either (2) we are allowed to modifed or (3) we are not. The flags
+ * you pass in will decide which files we are allowed to modify.
+ *
+ * By default, checkout is not allowed to modify any files. Anything
+ * needing a change would be considered a conflict.
+ *
+ * GIT_CHECKOUT_UPDATE_UNMODIFIED means that checkout is allowed to update
+ * any file where the working directory content matches the HEAD
+ * (e.g. either the files match or the file is absent in both places).
+ *
+ * GIT_CHECKOUT_UPDATE_MISSING means checkout can create a missing file
+ * that exists in the index and does not exist in the working directory.
+ * This is usually desirable for initial checkout, etc. Technically, the
+ * missing file differs from the HEAD, which is why this is separate.
+ *
+ * GIT_CHECKOUT_UPDATE_MODIFIED means checkout is allowed to update files
+ * where the working directory does not match the HEAD so long as the file
+ * actually exists in the HEAD. This option implies UPDATE_UNMODIFIED.
+ *
+ * GIT_CHECKOUT_UPDATE_UNTRACKED means checkout is allowed to update files
+ * even if there is a working directory version that does not exist in the
+ * HEAD (i.e. the file was independently created in the workdir). This
+ * implies UPDATE_UNMODIFIED | UPDATE_MISSING (but *not* UPDATE_MODIFIED).
+ *
+ *
+ * On top of these three basic strategies, there are some modifiers
+ * options that can be applied:
+ *
+ * If any files need update but are disallowed by the strategy, normally
+ * checkout calls the conflict callback (if given) and then aborts.
+ * GIT_CHECKOUT_ALLOW_CONFLICTS means it is okay to update the files that
+ * are allowed by the strategy even if there are conflicts. The conflict
+ * callbacks are still made, but non-conflicting files will be updated.
+ *
+ * Any unmerged entries in the index are automatically considered conflicts.
+ * If you want to proceed anyhow and just skip unmerged entries, you can use
+ * GIT_CHECKOUT_SKIP_UNMERGED which is less dangerous than just allowing all
+ * conflicts. Alternatively, use GIT_CHECKOUT_USE_OURS to proceed and
+ * checkout the stage 2 ("ours") version. GIT_CHECKOUT_USE_THEIRS means to
+ * proceed and use the stage 3 ("theirs") version.
+ *
+ * GIT_CHECKOUT_UPDATE_ONLY means that update is not allowed to create new
+ * files or delete old ones, only update existing content. With this
+ * flag, files that needs to be created or deleted are not conflicts -
+ * they are just skipped. This also skips typechanges to existing files
+ * (because the old would have to be removed).
+ *
+ * GIT_CHECKOUT_REMOVE_UNTRACKED means that files in the working directory
+ * that are untracked (and not ignored) will be removed altogether. These
+ * untracked files (that do not shadow index entries) are not considered
+ * conflicts and would normally be ignored.
+ *
+ *
+ * Checkout is "semi-atomic" as in it will go through the work to be done
+ * before making any changes and if may decide to abort if there are
+ * conflicts, or you can use the conflict callback to explicitly abort the
+ * action before any updates are made. Despite this, if a second process
+ * is modifying the filesystem while checkout is running, it can't
+ * guarantee that the choices is makes while initially examining the
+ * filesystem are still going to be correct as it applies them.
*/
typedef enum {
- /** Checkout does not update any files in the working directory. */
- GIT_CHECKOUT_DEFAULT = (1 << 0),
+ GIT_CHECKOUT_DEFAULT = 0, /** default is a dry run, no actual updates */
- /** When a file exists and is modified, replace it with new version. */
- GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1),
+ /** Allow update of entries where working dir matches HEAD. */
+ GIT_CHECKOUT_UPDATE_UNMODIFIED = (1u << 0),
- /** When a file does not exist in the working directory, create it. */
- GIT_CHECKOUT_CREATE_MISSING = (1 << 2),
+ /** Allow update of entries where working dir does not have file. */
+ GIT_CHECKOUT_UPDATE_MISSING = (1u << 1),
+
+ /** Allow safe updates that cannot overwrite uncommited data */
+ GIT_CHECKOUT_SAFE =
+ (GIT_CHECKOUT_UPDATE_UNMODIFIED | GIT_CHECKOUT_UPDATE_MISSING),
+
+ /** Allow update of entries in working dir that are modified from HEAD. */
+ GIT_CHECKOUT_UPDATE_MODIFIED = (1u << 2),
+
+ /** Update existing untracked files that are now present in the index. */
+ GIT_CHECKOUT_UPDATE_UNTRACKED = (1u << 3),
+
+ /** Allow all updates to force working directory to look like index */
+ GIT_CHECKOUT_FORCE =
+ (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED),
+
+ /** Allow checkout to make updates even if conflicts are found */
+ GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4),
+
+ /** Remove untracked files not in index (that are not ignored) */
+ GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5),
+
+ /** Only update existing files, don't create new ones */
+ GIT_CHECKOUT_UPDATE_ONLY = (1u << 6),
+
+ /**
+ * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
+ */
+
+ /** Allow checkout to skip unmerged files (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10),
+ /** For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_USE_OURS = (1u << 11),
+ /** For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_USE_THEIRS = (1u << 12),
+
+ /** Recursively checkout submodules with same options (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16),
+ /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */
+ GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17),
- /** If an untracked file in found in the working dir, delete it. */
- GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 3),
} git_checkout_strategy_t;
/**
@@ -47,31 +148,38 @@ typedef enum {
* Use zeros to indicate default settings.
*/
typedef struct git_checkout_opts {
- unsigned int checkout_strategy; /** default: GIT_CHECKOUT_DEFAULT */
+ unsigned int checkout_strategy; /** default will be a dry run */
+
int disable_filters; /** don't apply filters like CRLF conversion */
int dir_mode; /** default is 0755 */
int file_mode; /** default is 0644 or 0755 as dictated by blob */
int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */
- /** Optional callback to notify the consumer of files that
- * haven't be checked out because a modified version of them
- * exist in the working directory.
- *
- * When provided, this callback will be invoked when the flag
- * GIT_CHECKOUT_OVERWRITE_MODIFIED isn't part of the checkout strategy.
+ /** Optional callback made on files where the index differs from the
+ * working directory but the rules do not allow update. Return a
+ * non-zero value to abort the checkout. All such callbacks will be
+ * made before any changes are made to the working directory.
*/
- int (* skipped_notify_cb)(
- const char *skipped_file,
- const git_oid *blob_oid,
- int file_mode,
+ int (*conflict_cb)(
+ const char *conflicting_path,
+ const git_oid *index_oid,
+ unsigned int index_mode,
+ unsigned int wd_mode,
void *payload);
+ void *conflict_payload;
- void *notify_payload;
+ /* Optional callback to notify the consumer of checkout progress. */
+ void (*progress_cb)(
+ const char *path,
+ size_t completed_steps,
+ size_t total_steps,
+ void *payload);
+ void *progress_payload;
- /** When not NULL, array of fnmatch patterns specifying
- * which paths should be taken into account
+ /** When not zeroed out, array of fnmatch patterns specifying which
+ * paths should be taken into account, otherwise all files.
*/
- git_strarray paths;
+ git_strarray paths;
} git_checkout_opts;
/**
@@ -80,29 +188,27 @@ typedef struct git_checkout_opts {
*
* @param repo repository to check out (must be non-bare)
* @param opts specifies checkout options (may be NULL)
- * @param stats structure through which progress information is reported
* @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
* branch, GIT_ERROR otherwise (use giterr_last for information
* about the error)
*/
GIT_EXTERN(int) git_checkout_head(
git_repository *repo,
- git_checkout_opts *opts,
- git_indexer_stats *stats);
+ git_checkout_opts *opts);
/**
* Updates files in the working tree to match the content of the index.
*
- * @param repo repository to check out (must be non-bare)
+ * @param repo repository into which to check out (must be non-bare)
+ * @param index index to be checked out (or NULL to use repository index)
* @param opts specifies checkout options (may be NULL)
- * @param stats structure through which progress information is reported
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information
* about the error)
*/
GIT_EXTERN(int) git_checkout_index(
git_repository *repo,
- git_checkout_opts *opts,
- git_indexer_stats *stats);
+ git_index *index,
+ git_checkout_opts *opts);
/**
* Updates files in the index and working tree to match the content of the
@@ -112,15 +218,13 @@ GIT_EXTERN(int) git_checkout_index(
* @param treeish a commit, tag or tree which content will be used to update
* the working directory
* @param opts specifies checkout options (may be NULL)
- * @param stats structure through which progress information is reported
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information
* about the error)
*/
GIT_EXTERN(int) git_checkout_tree(
git_repository *repo,
git_object *treeish,
- git_checkout_opts *opts,
- git_indexer_stats *stats);
+ git_checkout_opts *opts);
/** @} */
GIT_END_DECL
diff --git a/include/git2/clone.h b/include/git2/clone.h
index c4dfc652b..7d8d32118 100644
--- a/include/git2/clone.h
+++ b/include/git2/clone.h
@@ -29,19 +29,22 @@ GIT_BEGIN_DECL
* @param out pointer that will receive the resulting repository object
* @param origin_url repository to clone from
* @param workdir_path local directory to clone to
- * @param fetch_stats pointer to structure that receives fetch progress
- * information (may be NULL)
+ * @param fetch_progress_cb optional callback for fetch progress. Be aware that
+ * this is called inline with network and indexing operations, so performance
+ * may be affected.
+ * @param fetch_progress_payload payload for fetch_progress_cb
* @param checkout_opts options for the checkout step. If NULL, no checkout
* is performed
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information
* about the error)
*/
-GIT_EXTERN(int) git_clone(git_repository **out,
- const char *origin_url,
- const char *workdir_path,
- git_indexer_stats *fetch_stats,
- git_indexer_stats *checkout_stats,
- git_checkout_opts *checkout_opts);
+GIT_EXTERN(int) git_clone(
+ git_repository **out,
+ const char *origin_url,
+ const char *workdir_path,
+ git_transfer_progress_callback fetch_progress_cb,
+ void *fetch_progress_payload,
+ git_checkout_opts *checkout_opts);
/**
* Create a bare clone of a remote repository.
@@ -49,13 +52,18 @@ GIT_EXTERN(int) git_clone(git_repository **out,
* @param out pointer that will receive the resulting repository object
* @param origin_url repository to clone from
* @param dest_path local directory to clone to
- * @param fetch_stats pointer to structure that receives fetch progress information (may be NULL)
+ * @param fetch_progress_cb optional callback for fetch progress. Be aware that
+ * this is called inline with network and indexing operations, so performance
+ * may be affected.
+ * @param fetch_progress_payload payload for fetch_progress_cb
* @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error)
*/
-GIT_EXTERN(int) git_clone_bare(git_repository **out,
- const char *origin_url,
- const char *dest_path,
- git_indexer_stats *fetch_stats);
+GIT_EXTERN(int) git_clone_bare(
+ git_repository **out,
+ const char *origin_url,
+ const char *dest_path,
+ git_transfer_progress_callback fetch_progress_cb,
+ void *fetch_progress_payload);
/** @} */
GIT_END_DECL
diff --git a/include/git2/config.h b/include/git2/config.h
index 67408f90f..8ec78e35c 100644
--- a/include/git2/config.h
+++ b/include/git2/config.h
@@ -56,6 +56,7 @@ struct git_config_file {
int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value);
int (*del)(struct git_config_file *, const char *key);
int (*foreach)(struct git_config_file *, const char *, int (*fn)(const git_config_entry *, void *), void *data);
+ int (*refresh)(struct git_config_file *);
void (*free)(struct git_config_file *);
};
@@ -134,19 +135,6 @@ GIT_EXTERN(int) git_config_find_system(char *system_config_path, size_t length);
GIT_EXTERN(int) git_config_open_default(git_config **out);
/**
- * Create a configuration file backend for ondisk files
- *
- * These are the normal `.gitconfig` files that Core Git
- * processes. Note that you first have to add this file to a
- * configuration object before you can query it for configuration
- * variables.
- *
- * @param out the new backend
- * @param path where the config file is located
- */
-GIT_EXTERN(int) git_config_file__ondisk(struct git_config_file **out, const char *path);
-
-/**
* Allocate a new configuration object
*
* This object is empty, so you have to add a file to it before you
@@ -201,7 +189,8 @@ GIT_EXTERN(int) git_config_add_file(
* @param force if a config file already exists for the given
* priority level, replace it
* @return 0 on success, GIT_EEXISTS when adding more than one file
- * for a given priority level (and force_replace set to 0), or error code
+ * for a given priority level (and force_replace set to 0),
+ * GIT_ENOTFOUND when the file doesn't exist or error code
*/
GIT_EXTERN(int) git_config_add_file_ondisk(
git_config *cfg,
@@ -209,7 +198,6 @@ GIT_EXTERN(int) git_config_add_file_ondisk(
unsigned int level,
int force);
-
/**
* Create a new config instance containing a single on-disk file
*
@@ -218,11 +206,12 @@ GIT_EXTERN(int) git_config_add_file_ondisk(
* - git_config_new
* - git_config_add_file_ondisk
*
- * @param cfg The configuration instance to create
+ * @param out The configuration instance to create
* @param path Path to the on-disk file to open
- * @return 0 or an error code
+ * @return 0 on success, GIT_ENOTFOUND when the file doesn't exist
+ * or an error code
*/
-GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path);
+GIT_EXTERN(int) git_config_open_ondisk(git_config **out, const char *path);
/**
* Build a single-level focused config object from a multi-level one.
@@ -243,6 +232,19 @@ GIT_EXTERN(int) git_config_open_level(
unsigned int level);
/**
+ * Reload changed config files
+ *
+ * A config file may be changed on disk out from under the in-memory
+ * config object. This function causes us to look for files that have
+ * been modified since we last loaded them and refresh the config with
+ * the latest information.
+ *
+ * @param cfg The configuration to refresh
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_config_refresh(git_config *cfg);
+
+/**
* Free the configuration and its associated memory and files
*
* @param cfg the configuration to free
@@ -260,7 +262,7 @@ GIT_EXTERN(void) git_config_free(git_config *cfg);
* @param name the variable's name
* @return 0 or an error code
*/
-GIT_EXTERN(int) git_config_get_config_entry(const git_config_entry **out, git_config *cfg, const char *name);
+GIT_EXTERN(int) git_config_get_entry(const git_config_entry **out, git_config *cfg, const char *name);
/**
* Get the value of an integer config variable.
diff --git a/include/git2/diff.h b/include/git2/diff.h
index 1932db029..47bfa5f69 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -33,7 +33,7 @@ GIT_BEGIN_DECL
* Flags for diff options. A combination of these flags can be passed
* in via the `flags` value in the `git_diff_options`.
*/
-enum {
+typedef enum {
/** Normal diff, the default */
GIT_DIFF_NORMAL = 0,
/** Reverse the sides of the diff */
@@ -86,7 +86,9 @@ enum {
* mode set to tree. Note: the tree SHA will not be available.
*/
GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 16),
-};
+ /** Ignore file mode changes */
+ GIT_DIFF_IGNORE_FILEMODE = (1 << 17),
+} git_diff_option_t;
/**
* Structure describing options about how the diff should be executed.
@@ -95,7 +97,7 @@ enum {
* values. Similarly, passing NULL for the options structure will
* give the defaults. The default values are marked below.
*
- * - flags: a combination of the GIT_DIFF_... values above
+ * - flags: a combination of the git_diff_option_t values above
* - context_lines: number of lines of context to show around diffs
* - interhunk_lines: min lines between diff hunks to merge them
* - old_prefix: "directory" to prefix to old file names (default "a")
@@ -124,7 +126,7 @@ typedef struct git_diff_list git_diff_list;
* Most of the flags are just for internal consumption by libgit2,
* but some of them may be interesting to external users.
*/
-enum {
+typedef enum {
GIT_DIFF_FILE_VALID_OID = (1 << 0), /** `oid` value is known correct */
GIT_DIFF_FILE_FREE_PATH = (1 << 1), /** `path` is allocated memory */
GIT_DIFF_FILE_BINARY = (1 << 2), /** should be considered binary data */
@@ -132,7 +134,7 @@ enum {
GIT_DIFF_FILE_FREE_DATA = (1 << 4), /** internal file data is allocated */
GIT_DIFF_FILE_UNMAP_DATA = (1 << 5), /** internal file data is mmap'ed */
GIT_DIFF_FILE_NO_DATA = (1 << 6), /** file data should not be loaded */
-};
+} git_diff_file_flag_t;
/**
* What type of change is described by a git_diff_delta?
@@ -218,7 +220,7 @@ typedef int (*git_diff_hunk_fn)(
* output callbacks to demarcate lines that are actually part of
* the file or hunk headers.
*/
-enum {
+typedef enum {
/* These values will be sent to `git_diff_data_fn` along with the line */
GIT_DIFF_LINE_CONTEXT = ' ',
GIT_DIFF_LINE_ADDITION = '+',
@@ -233,7 +235,7 @@ enum {
GIT_DIFF_LINE_FILE_HDR = 'F',
GIT_DIFF_LINE_HUNK_HDR = 'H',
GIT_DIFF_LINE_BINARY = 'B'
-};
+} git_diff_line_t;
/**
* When iterating over a diff, callback that will be made per text diff
@@ -259,6 +261,46 @@ typedef int (*git_diff_data_fn)(
*/
typedef struct git_diff_patch git_diff_patch;
+/**
+ * Flags to control the behavior of diff rename/copy detection.
+ */
+typedef enum {
+ /** look for renames? (`--find-renames`) */
+ GIT_DIFF_FIND_RENAMES = (1 << 0),
+ /** consider old size of modified for renames? (`--break-rewrites=N`) */
+ GIT_DIFF_FIND_RENAMES_FROM_REWRITES = (1 << 1),
+
+ /** look for copies? (a la `--find-copies`) */
+ GIT_DIFF_FIND_COPIES = (1 << 2),
+ /** consider unmodified as copy sources? (`--find-copies-harder`) */
+ GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1 << 3),
+
+ /** split large rewrites into delete/add pairs (`--break-rewrites=/M`) */
+ GIT_DIFF_FIND_AND_BREAK_REWRITES = (1 << 4),
+} git_diff_find_t;
+
+/**
+ * Control behavior of rename and copy detection
+ */
+typedef struct {
+ /** Combination of git_diff_find_t values (default FIND_RENAMES) */
+ unsigned int flags;
+
+ /** Similarity to consider a file renamed (default 50) */
+ unsigned int rename_threshold;
+ /** Similarity of modified to be eligible rename source (default 50) */
+ unsigned int rename_from_rewrite_threshold;
+ /** Similarity to consider a file a copy (default 50) */
+ unsigned int copy_threshold;
+ /** Similarity to split modify into delete/add pair (default 60) */
+ unsigned int break_rewrite_threshold;
+
+ /** Maximum similarity sources to examine (a la diff's `-l` option or
+ * the `diff.renameLimit` config) (default 200)
+ */
+ unsigned int target_limit;
+} git_diff_find_options;
+
/** @name Diff List Generator Functions
*
@@ -277,52 +319,56 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff);
*
* This is equivalent to `git diff <treeish> <treeish>`
*
+ * @param diff Output pointer to a git_diff_list pointer to be allocated.
* @param repo The repository containing the trees.
- * @param opts Structure with options to influence diff or NULL for defaults.
* @param old_tree A git_tree object to diff from.
* @param new_tree A git_tree object to diff to.
- * @param diff A pointer to a git_diff_list pointer that will be allocated.
+ * @param opts Structure with options to influence diff or NULL for defaults.
*/
GIT_EXTERN(int) git_diff_tree_to_tree(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
git_tree *old_tree,
git_tree *new_tree,
- git_diff_list **diff);
+ const git_diff_options *opts); /**< can be NULL for defaults */
/**
- * Compute a difference between a tree and the index.
+ * Compute a difference between a tree and the repository index.
*
* This is equivalent to `git diff --cached <treeish>` or if you pass
* the HEAD tree, then like `git diff --cached`.
*
+ * @param diff Output pointer to a git_diff_list pointer to be allocated.
* @param repo The repository containing the tree and index.
- * @param opts Structure with options to influence diff or NULL for defaults.
* @param old_tree A git_tree object to diff from.
- * @param diff A pointer to a git_diff_list pointer that will be allocated.
+ * @param index The index to diff with; repo index used if NULL.
+ * @param opts Structure with options to influence diff or NULL for defaults.
*/
GIT_EXTERN(int) git_diff_index_to_tree(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
git_tree *old_tree,
- git_diff_list **diff);
+ git_index *index,
+ const git_diff_options *opts); /**< can be NULL for defaults */
/**
- * Compute a difference between the working directory and the index.
+ * Compute a difference between the working directory and the repository index.
*
* This matches the `git diff` command. See the note below on
* `git_diff_workdir_to_tree` for a discussion of the difference between
* `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`
* using libgit2.
*
+ * @param diff Output pointer to a git_diff_list pointer to be allocated.
* @param repo The repository.
+ * @param index The index to diff from; repo index used if NULL.
* @param opts Structure with options to influence diff or NULL for defaults.
- * @param diff A pointer to a git_diff_list pointer that will be allocated.
*/
GIT_EXTERN(int) git_diff_workdir_to_index(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
- git_diff_list **diff);
+ git_index *index,
+ const git_diff_options *opts); /**< can be NULL for defaults */
/**
* Compute a difference between the working directory and a tree.
@@ -346,16 +392,16 @@ GIT_EXTERN(int) git_diff_workdir_to_index(
* The tree-to-workdir diff for that file is 'modified', but core git would
* show status 'deleted' since there is a pending deletion in the index.
*
+ * @param diff A pointer to a git_diff_list pointer that will be allocated.
* @param repo The repository containing the tree.
- * @param opts Structure with options to influence diff or NULL for defaults.
* @param old_tree A git_tree object to diff from.
- * @param diff A pointer to a git_diff_list pointer that will be allocated.
+ * @param opts Structure with options to influence diff or NULL for defaults.
*/
GIT_EXTERN(int) git_diff_workdir_to_tree(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
git_tree *old_tree,
- git_diff_list **diff);
+ const git_diff_options *opts); /**< can be NULL for defaults */
/**
* Merge one diff list into another.
@@ -374,6 +420,22 @@ GIT_EXTERN(int) git_diff_merge(
git_diff_list *onto,
const git_diff_list *from);
+/**
+ * Transform a diff list marking file renames, copies, etc.
+ *
+ * This modifies a diff list in place, replacing old entries that look
+ * like renames or copies with new entries reflecting those changes.
+ * This also will, if requested, break modified files into add/remove
+ * pairs if the amount of change is above a threshold.
+ *
+ * @param diff Diff list to run detection algorithms on
+ * @param options Control how detection should be run, NULL for defaults
+ * @return 0 on success, -1 on failure
+ */
+GIT_EXTERN(int) git_diff_find_similar(
+ git_diff_list *diff,
+ git_diff_find_options *options);
+
/**@}*/
@@ -603,6 +665,34 @@ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(
size_t hunk_idx,
size_t line_of_hunk);
+/**
+ * Serialize the patch to text via callback.
+ *
+ * Returning a non-zero value from the callback will terminate the iteration
+ * and cause this return `GIT_EUSER`.
+ *
+ * @param patch A git_diff_patch representing changes to one file
+ * @param cb_data Reference pointer that will be passed to your callbacks.
+ * @param print_cb Callback function to output lines of the patch. Will be
+ * called for file headers, hunk headers, and diff lines.
+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code
+ */
+GIT_EXTERN(int) git_diff_patch_print(
+ git_diff_patch *patch,
+ void *cb_data,
+ git_diff_data_fn print_cb);
+
+/**
+ * Get the content of a patch as a single diff text.
+ *
+ * @param string Allocated string; caller must free.
+ * @param patch A git_diff_patch representing changes to one file
+ * @return 0 on success, <0 on failure.
+ */
+GIT_EXTERN(int) git_diff_patch_to_str(
+ char **string,
+ git_diff_patch *patch);
+
/**@}*/
diff --git a/include/git2/errors.h b/include/git2/errors.h
index 38b7fe0ae..45e04578d 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -28,6 +28,7 @@ enum {
GIT_EUSER = -7,
GIT_EBAREREPO = -8,
GIT_EORPHANEDHEAD = -9,
+ GIT_EUNMERGED = -10,
GIT_PASSTHROUGH = -30,
GIT_ITEROVER = -31,
@@ -58,6 +59,8 @@ typedef enum {
GITERR_SSL,
GITERR_SUBMODULE,
GITERR_THREAD,
+ GITERR_STASH,
+ GITERR_CHECKOUT,
} git_error_t;
/**
diff --git a/include/git2/index.h b/include/git2/index.h
index 062932e1a..8e1a7e521 100644
--- a/include/git2/index.h
+++ b/include/git2/index.h
@@ -84,12 +84,12 @@ typedef struct git_index_entry {
char *path;
} git_index_entry;
-/** Representation of an unmerged file entry in the index. */
-typedef struct git_index_entry_unmerged {
+/** Representation of a resolve undo entry in the index. */
+typedef struct git_index_reuc_entry {
unsigned int mode[3];
git_oid oid[3];
char *path;
-} git_index_entry_unmerged;
+} git_index_reuc_entry;
/** Capabilities of system that affect index actions. */
enum {
@@ -99,6 +99,12 @@ enum {
GIT_INDEXCAP_FROM_OWNER = ~0u
};
+/** @name Index File Functions
+ *
+ * These functions work on the index file itself.
+ */
+/**@{*/
+
/**
* Create a new bare Git index object as a memory representation
* of the Git index file in 'index_path', without a repository
@@ -120,13 +126,17 @@ enum {
GIT_EXTERN(int) git_index_open(git_index **index, const char *index_path);
/**
- * Clear the contents (all the entries) of an index object.
- * This clears the index object in memory; changes must be manually
- * written to disk for them to take effect.
+ * Create an in-memory index object.
*
- * @param index an existing index object
+ * This index object cannot be read/written to the filesystem,
+ * but may be used to perform in-memory index operations.
+ *
+ * The index must be freed once it's no longer in use.
+ *
+ * @param index the pointer for the new index
+ * @return 0 or an error code
*/
-GIT_EXTERN(void) git_index_clear(git_index *index);
+GIT_EXTERN(int) git_index_new(git_index **index);
/**
* Free an existing index object.
@@ -136,6 +146,14 @@ GIT_EXTERN(void) git_index_clear(git_index *index);
GIT_EXTERN(void) git_index_free(git_index *index);
/**
+ * Get the repository this index relates to
+ *
+ * @param index The index
+ * @return A pointer to the repository
+ */
+GIT_EXTERN(git_repository *) git_index_owner(const git_index *index);
+
+/**
* Read index capabilities flags.
*
* @param index An existing index object
@@ -175,44 +193,130 @@ GIT_EXTERN(int) git_index_read(git_index *index);
GIT_EXTERN(int) git_index_write(git_index *index);
/**
- * Find the first index of any entries which point to given
- * path in the Git index.
+ * Read a tree into the index file with stats
+ *
+ * The current index contents will be replaced by the specified tree.
*
* @param index an existing index object
- * @param path path to search
- * @return an index >= 0 if found, -1 otherwise
+ * @param tree tree to read
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_find(git_index *index, const char *path);
+GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree);
+
+/**
+ * Write the index as a tree
+ *
+ * This method will scan the index and write a representation
+ * of its current state back to disk; it recursively creates
+ * tree objects for each of the subtrees stored in the index,
+ * but only returns the OID of the root tree. This is the OID
+ * that can be used e.g. to create a commit.
+ *
+ * The index instance cannot be bare, and needs to be associated
+ * to an existing repository.
+ *
+ * The index must not contain any file in conflict.
+ *
+ * @param oid Pointer where to store the OID of the written tree
+ * @param index Index to write
+ * @return 0 on success, GIT_EUNMERGED when the index is not clean
+ * or an error code
+ */
+GIT_EXTERN(int) git_index_write_tree(git_oid *oid, git_index *index);
+
+/**
+ * Write the index as a tree to the given repository
+ *
+ * This method will do the same as `git_index_write_tree`, but
+ * letting the user choose the repository where the tree will
+ * be written.
+ *
+ * The index must not contain any file in conflict.
+ *
+ * @param oid Pointer where to store OID of the the written tree
+ * @param index Index to write
+ * @param repo Repository where to write the tree
+ * @return 0 on success, GIT_EUNMERGED when the index is not clean
+ * or an error code
+ */
+GIT_EXTERN(int) git_index_write_tree_to(git_oid *oid, git_index *index, git_repository *repo);
+
+/**@}*/
+
+/** @name Raw Index Entry Functions
+ *
+ * These functions work on index entries, and allow for raw manipulation
+ * of the entries.
+ */
+/**@{*/
+
+/* Index entry manipulation */
/**
- * Remove all entries with equal path except last added
+ * Get the count of entries currently in the index
*
* @param index an existing index object
+ * @return integer of count of current entries
*/
-GIT_EXTERN(void) git_index_uniq(git_index *index);
+GIT_EXTERN(unsigned int) git_index_entrycount(git_index *index);
/**
- * Add or update an index entry from a file in disk
+ * Clear the contents (all the entries) of an index object.
+ * This clears the index object in memory; changes must be manually
+ * written to disk for them to take effect.
*
- * The file `path` must be relative to the repository's
- * working folder and must be readable.
+ * @param index an existing index object
+ */
+GIT_EXTERN(void) git_index_clear(git_index *index);
+
+/**
+ * Get a pointer to one of the entries in the index
*
- * This method will fail in bare index instances.
+ * The values of this entry can be modified (except the path)
+ * and the changes will be written back to disk on the next
+ * write() call.
*
- * This forces the file to be added to the index, not looking
- * at gitignore rules. Those rules can be evaluated through
- * the git_status APIs (in status.h) before calling this.
+ * The entry should not be freed by the caller.
*
* @param index an existing index object
- * @param path filename to add
- * @param stage stage for the entry
+ * @param n the position of the entry
+ * @return a pointer to the entry; NULL if out of bounds
+ */
+GIT_EXTERN(git_index_entry *) git_index_get_byindex(git_index *index, size_t n);
+
+/**
+ * Get a pointer to one of the entries in the index
+ *
+ * The values of this entry can be modified (except the path)
+ * and the changes will be written back to disk on the next
+ * write() call.
+ *
+ * The entry should not be freed by the caller.
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @param stage stage to search
+ * @return a pointer to the entry; NULL if it was not found
+ */
+GIT_EXTERN(git_index_entry *) git_index_get_bypath(git_index *index, const char *path, int stage);
+
+/**
+ * Remove an entry from the index
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @param stage stage to search
* @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage);
+GIT_EXTERN(int) git_index_remove(git_index *index, const char *path, int stage);
/**
* Add or update an index entry from an in-memory struct
*
+ * If a previous index entry exists that has the same path and stage
+ * as the given 'source_entry', it will be replaced. Otherwise, the
+ * 'source_entry' will be added.
+ *
* A full copy (including the 'path' string) of the given
* 'source_entry' will be inserted on the index.
*
@@ -220,133 +324,214 @@ GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage);
* @param source_entry new entry object
* @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_add2(git_index *index, const git_index_entry *source_entry);
+GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_entry);
/**
- * Add (append) an index entry from a file in disk
+ * Return the stage number from a git index entry
*
- * A new entry will always be inserted into the index;
- * if the index already contains an entry for such
- * path, the old entry will **not** be replaced.
+ * This entry is calculated from the entry's flag
+ * attribute like this:
+ *
+ * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT
+ *
+ * @param entry The entry
+ * @returns the stage number
+ */
+GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);
+
+/**@}*/
+
+/** @name Workdir Index Entry Functions
+ *
+ * These functions work on index entries specifically in the working
+ * directory (ie, stage 0).
+ */
+/**@{*/
+
+/**
+ * Add or update an index entry from a file in disk
*
* The file `path` must be relative to the repository's
* working folder and must be readable.
*
* This method will fail in bare index instances.
*
+ * This forces the file to be added to the index, not looking
+ * at gitignore rules. Those rules can be evaluated through
+ * the git_status APIs (in status.h) before calling this.
+ *
+ * If this file currently is the result of a merge conflict, this
+ * file will no longer be marked as conflicting. The data about
+ * the conflict will be moved to the "resolve undo" (REUC) section.
+ *
* @param index an existing index object
* @param path filename to add
- * @param stage stage for the entry
* @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage);
+GIT_EXTERN(int) git_index_add_from_workdir(git_index *index, const char *path);
/**
- * Add (append) an index entry from an in-memory struct
+ * Find the first index of any entries which point to given
+ * path in the Git index.
+ *
+ * @param index an existing index object
+ * @param path path to search
+ * @return an index >= 0 if found, -1 otherwise
+ */
+GIT_EXTERN(int) git_index_find(git_index *index, const char *path);
+
+/**@}*/
+
+/** @name Conflict Index Entry Functions
*
- * A new entry will always be inserted into the index;
- * if the index already contains an entry for the path
- * in the `entry` struct, the old entry will **not** be
- * replaced.
+ * These functions work on conflict index entries specifically (ie, stages 1-3)
+ */
+/**@{*/
+
+/**
+ * Add or update index entries to represent a conflict
*
- * A full copy (including the 'path' string) of the given
- * 'source_entry' will be inserted on the index.
+ * The entries are the entries from the tree included in the merge. Any
+ * entry may be null to indicate that that file was not present in the
+ * trees during the merge. For example, ancestor_entry may be NULL to
+ * indicate that a file was added in both branches and must be resolved.
*
* @param index an existing index object
- * @param source_entry new entry object
+ * @param ancestor_entry the entry data for the ancestor of the conflict
+ * @param our_entry the entry data for our side of the merge conflict
+ * @param their_entry the entry data for their side of the merge conflict
* @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *source_entry);
+GIT_EXTERN(int) git_index_conflict_add(git_index *index,
+ const git_index_entry *ancestor_entry,
+ const git_index_entry *our_entry,
+ const git_index_entry *their_entry);
/**
- * Remove an entry from the index
+ * Get the index entries that represent a conflict of a single file.
+ *
+ * The values of this entry can be modified (except the paths)
+ * and the changes will be written back to disk on the next
+ * write() call.
*
+ * @param ancestor_out Pointer to store the ancestor entry
+ * @param our_out Pointer to store the our entry
+ * @param their_out Pointer to store the their entry
* @param index an existing index object
- * @param position position of the entry to remove
- * @return 0 or an error code
+ * @param path path to search
*/
-GIT_EXTERN(int) git_index_remove(git_index *index, int position);
+GIT_EXTERN(int) git_index_conflict_get(git_index_entry **ancestor_out, git_index_entry **our_out, git_index_entry **their_out, git_index *index, const char *path);
+/**
+ * Removes the index entries that represent a conflict of a single file.
+ *
+ * @param index an existing index object
+ * @param path to search
+ */
+GIT_EXTERN(int) git_index_conflict_remove(git_index *index, const char *path);
/**
- * Get a pointer to one of the entries in the index
+ * Remove all conflicts in the index (entries with a stage greater than 0.)
*
- * This entry can be modified, and the changes will be written
- * back to disk on the next write() call.
+ * @param index an existing index object
+ */
+GIT_EXTERN(void) git_index_conflict_cleanup(git_index *index);
+
+/**
+ * Determine if the index contains entries representing file conflicts.
*
- * The entry should not be freed by the caller.
+ * @return 1 if at least one conflict is found, 0 otherwise.
+ */
+GIT_EXTERN(int) git_index_has_conflicts(git_index *index);
+
+/**@}*/
+
+/** @name Resolve Undo (REUC) index entry manipulation.
*
- * @param index an existing index object
- * @param n the position of the entry
- * @return a pointer to the entry; NULL if out of bounds
+ * These functions work on the Resolve Undo index extension and contains
+ * data about the original files that led to a merge conflict.
*/
-GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, size_t n);
+/**@{*/
/**
- * Get the count of entries currently in the index
+ * Get the count of resolve undo entries currently in the index.
*
* @param index an existing index object
- * @return integer of count of current entries
+ * @return integer of count of current resolve undo entries
*/
-GIT_EXTERN(unsigned int) git_index_entrycount(git_index *index);
+GIT_EXTERN(unsigned int) git_index_reuc_entrycount(git_index *index);
/**
- * Get the count of unmerged entries currently in the index
+ * Finds the resolve undo entry that points to the given path in the Git
+ * index.
*
* @param index an existing index object
- * @return integer of count of current unmerged entries
+ * @param path path to search
+ * @return an index >= 0 if found, -1 otherwise
*/
-GIT_EXTERN(unsigned int) git_index_entrycount_unmerged(git_index *index);
+GIT_EXTERN(int) git_index_reuc_find(git_index *index, const char *path);
/**
- * Get an unmerged entry from the index.
+ * Get a resolve undo entry from the index.
*
* The returned entry is read-only and should not be modified
* of freed by the caller.
*
* @param index an existing index object
* @param path path to search
- * @return the unmerged entry; NULL if not found
+ * @return the resolve undo entry; NULL if not found
*/
-GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_bypath(git_index *index, const char *path);
+GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_bypath(git_index *index, const char *path);
/**
- * Get an unmerged entry from the index.
+ * Get a resolve undo entry from the index.
*
* The returned entry is read-only and should not be modified
* of freed by the caller.
*
* @param index an existing index object
* @param n the position of the entry
- * @return a pointer to the unmerged entry; NULL if out of bounds
+ * @return a pointer to the resolve undo entry; NULL if out of bounds
*/
-GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_index *index, size_t n);
+GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_byindex(git_index *index, size_t n);
/**
- * Return the stage number from a git index entry
+ * Adds an resolve undo entry for a file based on the given parameters.
*
- * This entry is calculated from the entry's flag
- * attribute like this:
+ * The resolve undo entry contains the OIDs of files that were involved
+ * in a merge conflict after the conflict has been resolved. This allows
+ * conflicts to be re-resolved later.
*
- * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT
+ * If there exists a resolve undo entry for the given path in the index,
+ * it will be removed.
*
- * @param entry The entry
- * @returns the stage number
+ * This method will fail in bare index instances.
+ *
+ * @param index an existing index object
+ * @param path filename to add
+ * @param ancestor_mode mode of the ancestor file
+ * @param ancestor_oid oid of the ancestor file
+ * @param our_mode mode of our file
+ * @param our_oid oid of our file
+ * @param their_mode mode of their file
+ * @param their_oid oid of their file
+ * @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry);
+GIT_EXTERN(int) git_index_reuc_add(git_index *index, const char *path,
+ int ancestor_mode, git_oid *ancestor_oid,
+ int our_mode, git_oid *our_oid,
+ int their_mode, git_oid *their_oid);
/**
- * Read a tree into the index file with stats
- *
- * The current index contents will be replaced by the specified tree. The total
- * node count is collected in stats.
+ * Remove an resolve undo entry from the index
*
* @param index an existing index object
- * @param tree tree to read
- * @param stats structure that receives the total node count (may be NULL)
+ * @param position position of the resolve undo entry to remove
* @return 0 or an error code
*/
-GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats);
+GIT_EXTERN(int) git_index_reuc_remove(git_index *index, int position);
+
+/**@}*/
/** @} */
GIT_END_DECL
diff --git a/include/git2/indexer.h b/include/git2/indexer.h
index 87f48fe27..a2a155473 100644
--- a/include/git2/indexer.h
+++ b/include/git2/indexer.h
@@ -16,13 +16,19 @@ GIT_BEGIN_DECL
* This is passed as the first argument to the callback to allow the
* user to see the progress.
*/
-typedef struct git_indexer_stats {
- unsigned int total;
- unsigned int processed;
- unsigned int received;
-} git_indexer_stats;
+typedef struct git_transfer_progress {
+ unsigned int total_objects;
+ unsigned int indexed_objects;
+ unsigned int received_objects;
+ size_t received_bytes;
+} git_transfer_progress;
+/**
+ * Type for progress callbacks during indexing
+ */
+typedef void (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload);
+
typedef struct git_indexer git_indexer;
typedef struct git_indexer_stream git_indexer_stream;
@@ -31,8 +37,14 @@ typedef struct git_indexer_stream git_indexer_stream;
*
* @param out where to store the indexer instance
* @param path to the directory where the packfile should be stored
+ * @param progress_cb function to call with progress information
+ * @param progress_payload payload for the progress callback
*/
-GIT_EXTERN(int) git_indexer_stream_new(git_indexer_stream **out, const char *path);
+GIT_EXTERN(int) git_indexer_stream_new(
+ git_indexer_stream **out,
+ const char *path,
+ git_transfer_progress_callback progress_cb,
+ void *progress_callback_payload);
/**
* Add data to the indexer
@@ -42,7 +54,7 @@ GIT_EXTERN(int) git_indexer_stream_new(git_indexer_stream **out, const char *pat
* @param size the size of the data
* @param stats stat storage
*/
-GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats);
+GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats);
/**
* Finalize the pack and index
@@ -51,7 +63,7 @@ GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data
*
* @param idx the indexer
*/
-GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats);
+GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats);
/**
* Get the packfile's hash
@@ -88,7 +100,7 @@ GIT_EXTERN(int) git_indexer_new(git_indexer **out, const char *packname);
* @param idx the indexer instance
* @param stats storage for the running state
*/
-GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_indexer_stats *stats);
+GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_transfer_progress *stats);
/**
* Write the index file to disk.
diff --git a/include/git2/odb.h b/include/git2/odb.h
index c6e73571b..4afa3b788 100644
--- a/include/git2/odb.h
+++ b/include/git2/odb.h
@@ -11,6 +11,7 @@
#include "types.h"
#include "oid.h"
#include "odb_backend.h"
+#include "indexer.h"
/**
* @file git2/odb.h
@@ -263,6 +264,26 @@ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_
GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid);
/**
+ * Open a stream for writing a pack file to the ODB.
+ *
+ * If the ODB layer understands pack files, then the given
+ * packfile will likely be streamed directly to disk (and a
+ * corresponding index created). If the ODB layer does not
+ * understand pack files, the objects will be stored in whatever
+ * format the ODB layer uses.
+ *
+ * @see git_odb_writepack
+ *
+ * @param writepack pointer to the writepack functions
+ * @param db object database where the stream will read from
+ * @param progress_cb function to call with progress information.
+ * Be aware that this is called inline with network and indexing operations,
+ * so performance may be affected.
+ * @param progress_payload payload for the progress callback
+ */
+GIT_EXTERN(int) git_odb_write_pack(git_odb_writepack **writepack, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload);
+
+/**
* Determine the object-ID (sha1 hash) of a data buffer
*
* The resulting SHA-1 OID will be the identifier for the data
diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h
index cb8069787..4df48d77e 100644
--- a/include/git2/odb_backend.h
+++ b/include/git2/odb_backend.h
@@ -10,6 +10,7 @@
#include "common.h"
#include "types.h"
#include "oid.h"
+#include "indexer.h"
/**
* @file git2/backend.h
@@ -21,6 +22,7 @@
GIT_BEGIN_DECL
struct git_odb_stream;
+struct git_odb_writepack;
/** An instance for a custom backend */
struct git_odb_backend {
@@ -75,11 +77,16 @@ struct git_odb_backend {
struct git_odb_backend *,
const git_oid *);
- int (*foreach)(
- struct git_odb_backend *,
- int (*cb)(git_oid *oid, void *data),
- void *data
- );
+ int (* foreach)(
+ struct git_odb_backend *,
+ int (*cb)(git_oid *oid, void *data),
+ void *data);
+
+ int (* writepack)(
+ struct git_odb_writepack **,
+ struct git_odb_backend *,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
void (* free)(struct git_odb_backend *);
};
@@ -102,6 +109,15 @@ struct git_odb_stream {
void (*free)(struct git_odb_stream *stream);
};
+/** A stream to write a pack file to the ODB */
+struct git_odb_writepack {
+ struct git_odb_backend *backend;
+
+ int (*add)(struct git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats);
+ int (*commit)(struct git_odb_writepack *writepack, git_transfer_progress *stats);
+ void (*free)(struct git_odb_writepack *writepack);
+};
+
GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir);
GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir, int compression_level, int do_fsync);
GIT_EXTERN(int) git_odb_backend_one_pack(git_odb_backend **backend_out, const char *index_file);
diff --git a/include/git2/pack.h b/include/git2/pack.h
index 748ad2e11..94d5fc6a1 100644
--- a/include/git2/pack.h
+++ b/include/git2/pack.h
@@ -78,6 +78,30 @@ GIT_EXTERN(int) git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *
GIT_EXTERN(int) git_packbuilder_write(git_packbuilder *pb, const char *file);
/**
+ * Create the new pack and pass each object to the callback
+ *
+ * @param pb the packbuilder
+ * @param cb the callback to call with each packed object's buffer
+ * @param data the callback's data
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *data), void *data);
+
+/**
+ * Get the total number of objects the packbuilder will write out
+ *
+ * @param pb the packbuilder
+ */
+GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb);
+
+/**
+ * Get the number of objects the packbuilder has already written out
+ *
+ * @param pb the packbuilder
+ */
+GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb);
+
+/**
* Free the packbuilder and all associated data
*
* @param pb The packbuilder
diff --git a/include/git2/reflog.h b/include/git2/reflog.h
index 447915ef8..72e1f1753 100644
--- a/include/git2/reflog.h
+++ b/include/git2/reflog.h
@@ -88,8 +88,12 @@ GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog);
/**
* Lookup an entry by its index
*
+ * Requesting the reflog entry with an index of 0 (zero) will
+ * return the most recently created entry.
+ *
* @param reflog a previously loaded reflog
- * @param idx the position to lookup
+ * @param idx the position of the entry to lookup. Should be greater than or
+ * equal to 0 (zero) and less than `git_reflog_entrycount()`.
* @return the entry; NULL if not found
*/
GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, size_t idx);
@@ -97,21 +101,23 @@ GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog
/**
* Remove an entry from the reflog by its index
*
- * To ensure there's no gap in the log history, set the `rewrite_previosu_entry` to 1.
- * When deleting entry `n`, member old_oid of entry `n-1` (if any) will be updated with
- * the value of memeber new_oid of entry `n+1`.
+ * To ensure there's no gap in the log history, set `rewrite_previous_entry`
+ * param value to 1. When deleting entry `n`, member old_oid of entry `n-1`
+ * (if any) will be updated with the value of member new_oid of entry `n+1`.
*
* @param reflog a previously loaded reflog.
*
- * @param idx the position of the entry to remove.
+ * @param idx the position of the entry to remove. Should be greater than or
+ * equal to 0 (zero) and less than `git_reflog_entrycount()`.
*
* @param rewrite_previous_entry 1 to rewrite the history; 0 otherwise.
*
- * @return 0 on success or an error code.
+ * @return 0 on success, GIT_ENOTFOUND if the entry doesn't exist
+ * or an error code.
*/
GIT_EXTERN(int) git_reflog_drop(
git_reflog *reflog,
- unsigned int idx,
+ size_t idx,
int rewrite_previous_entry);
/**
diff --git a/include/git2/refs.h b/include/git2/refs.h
index 001c2bcc7..bc3f44482 100644
--- a/include/git2/refs.h
+++ b/include/git2/refs.h
@@ -22,13 +22,16 @@
GIT_BEGIN_DECL
/**
- * Lookup a reference by its name in a repository.
+ * Lookup a reference by name in a repository.
*
- * The generated reference must be freed by the user.
+ * The returned reference must be freed by the user.
+ *
+ * See `git_reference_create_symbolic()` for documentation about valid
+ * reference names.
*
* @param reference_out pointer to the looked-up reference
* @param repo the repository to look up the reference
- * @param name the long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...)
+ * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...)
* @return 0 or an error code
*/
GIT_EXTERN(int) git_reference_lookup(git_reference **reference_out, git_repository *repo, const char *name);
@@ -36,6 +39,10 @@ GIT_EXTERN(int) git_reference_lookup(git_reference **reference_out, git_reposito
/**
* Lookup a reference by name and resolve immediately to OID.
*
+ * This function provides a quick way to resolve a reference name straight
+ * through to the object id that it refers to. This avoids having to
+ * allocate or free any `git_reference` objects for simple situations.
+ *
* @param oid Pointer to oid to be filled in
* @param repo The repository in which to look up the reference
* @param name The long name for the reference
@@ -47,13 +54,24 @@ GIT_EXTERN(int) git_reference_name_to_oid(
/**
* Create a new symbolic reference.
*
- * The reference will be created in the repository and written
- * to the disk.
+ * A symbolic reference is a reference name that refers to another
+ * reference name. If the other name moves, the symbolic name will move,
+ * too. As a simple example, the "HEAD" reference might refer to
+ * "refs/heads/master" while on the "master" branch of a repository.
+ *
+ * The symbolic reference will be created in the repository and written to
+ * the disk. The generated reference object must be freed by the user.
+ *
+ * Valid reference names must follow one of two patterns:
*
- * The generated reference must be freed by the user.
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
*
- * If `force` is true and there already exists a reference
- * with the same name, it will be overwritten.
+ * This function will return an error if a reference already exists with the
+ * given name unless `force` is true, in which case it will be overwritten.
*
* @param ref_out Pointer to the newly created reference
* @param repo Repository where that reference will live
@@ -65,15 +83,27 @@ GIT_EXTERN(int) git_reference_name_to_oid(
GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force);
/**
- * Create a new object id reference.
+ * Create a new direct reference.
*
- * The reference will be created in the repository and written
- * to the disk.
+ * A direct reference (also called an object id reference) refers directly
+ * to a specific object id (a.k.a. OID or SHA) in the repository. The id
+ * permanently refers to the object (although the reference itself can be
+ * moved). For example, in libgit2 the direct ref "refs/tags/v0.17.0"
+ * refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977.
*
- * The generated reference must be freed by the user.
+ * The direct reference will be created in the repository and written to
+ * the disk. The generated reference object must be freed by the user.
*
- * If `force` is true and there already exists a reference
- * with the same name, it will be overwritten.
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
+ *
+ * This function will return an error if a reference already exists with the
+ * given name unless `force` is true, in which case it will be overwritten.
*
* @param ref_out Pointer to the newly created reference
* @param repo Repository where that reference will live
@@ -85,9 +115,14 @@ GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repos
GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force);
/**
- * Get the OID pointed to by a reference.
+ * Get the OID pointed to by a direct reference.
+ *
+ * Only available if the reference is direct (i.e. an object id reference,
+ * not a symbolic one).
*
- * Only available if the reference is direct (i.e. not symbolic)
+ * To find the OID of a symbolic ref, call `git_reference_resolve()` and
+ * then this function (or maybe use `git_reference_name_to_oid()` to
+ * directly resolve a reference name all the way through to an OID).
*
* @param ref The reference
* @return a pointer to the oid if available, NULL otherwise
@@ -95,9 +130,9 @@ GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository
GIT_EXTERN(const git_oid *) git_reference_oid(git_reference *ref);
/**
- * Get full name to the reference pointed by this reference
+ * Get full name to the reference pointed to by a symbolic reference.
*
- * Only available if the reference is symbolic
+ * Only available if the reference is symbolic.
*
* @param ref The reference
* @return a pointer to the name if available, NULL otherwise
@@ -105,7 +140,7 @@ GIT_EXTERN(const git_oid *) git_reference_oid(git_reference *ref);
GIT_EXTERN(const char *) git_reference_target(git_reference *ref);
/**
- * Get the type of a reference
+ * Get the type of a reference.
*
* Either direct (GIT_REF_OID) or symbolic (GIT_REF_SYMBOLIC)
*
@@ -115,7 +150,9 @@ GIT_EXTERN(const char *) git_reference_target(git_reference *ref);
GIT_EXTERN(git_ref_t) git_reference_type(git_reference *ref);
/**
- * Get the full name of a reference
+ * Get the full name of a reference.
+ *
+ * See `git_reference_create_symbolic()` for rules about valid names.
*
* @param ref The reference
* @return the full name for the ref
@@ -123,18 +160,16 @@ GIT_EXTERN(git_ref_t) git_reference_type(git_reference *ref);
GIT_EXTERN(const char *) git_reference_name(git_reference *ref);
/**
- * Resolve a symbolic reference
+ * Resolve a symbolic reference to a direct reference.
*
- * This method iteratively peels a symbolic reference
- * until it resolves to a direct reference to an OID.
+ * This method iteratively peels a symbolic reference until it resolves to
+ * a direct reference to an OID.
*
- * The peeled reference is returned in the `resolved_ref`
- * argument, and must be freed manually once it's no longer
- * needed.
+ * The peeled reference is returned in the `resolved_ref` argument, and
+ * must be freed manually once it's no longer needed.
*
- * If a direct reference is passed as an argument,
- * a copy of that reference is returned. This copy must
- * be manually freed too.
+ * If a direct reference is passed as an argument, a copy of that
+ * reference is returned. This copy must be manually freed too.
*
* @param resolved_ref Pointer to the peeled reference
* @param ref The reference
@@ -143,7 +178,7 @@ GIT_EXTERN(const char *) git_reference_name(git_reference *ref);
GIT_EXTERN(int) git_reference_resolve(git_reference **resolved_ref, git_reference *ref);
/**
- * Get the repository where a reference resides
+ * Get the repository where a reference resides.
*
* @param ref The reference
* @return a pointer to the repo
@@ -153,11 +188,9 @@ GIT_EXTERN(git_repository *) git_reference_owner(git_reference *ref);
/**
* Set the symbolic target of a reference.
*
- * The reference must be a symbolic reference, otherwise
- * this method will fail.
+ * The reference must be a symbolic reference, otherwise this will fail.
*
- * The reference will be automatically updated in
- * memory and on disk.
+ * The reference will be automatically updated in memory and on disk.
*
* @param ref The reference
* @param target The new target for the reference
@@ -168,11 +201,9 @@ GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const char *target)
/**
* Set the OID target of a reference.
*
- * The reference must be a direct reference, otherwise
- * this method will fail.
+ * The reference must be a direct reference, otherwise this will fail.
*
- * The reference will be automatically updated in
- * memory and on disk.
+ * The reference will be automatically updated in memory and on disk.
*
* @param ref The reference
* @param id The new target OID for the reference
@@ -181,7 +212,7 @@ GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const char *target)
GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id);
/**
- * Rename an existing reference
+ * Rename an existing reference.
*
* This method works for both direct and symbolic references.
* The new name will be checked for validity and may be
@@ -189,8 +220,7 @@ GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id);
*
* The given git_reference will be updated in place.
*
- * The reference will be immediately renamed in-memory
- * and on disk.
+ * The reference will be immediately renamed in-memory and on disk.
*
* If the `force` flag is not enabled, and there's already
* a reference with the given name, the renaming will fail.
@@ -209,12 +239,12 @@ GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id);
GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, int force);
/**
- * Delete an existing reference
+ * Delete an existing reference.
*
* This method works for both direct and symbolic references.
*
- * The reference will be immediately removed on disk and from
- * memory. The given reference pointer will no longer be valid.
+ * The reference will be immediately removed on disk and from memory
+ * (i.e. freed). The given reference pointer will no longer be valid.
*
* @param ref The reference to remove
* @return 0 or an error code
@@ -222,7 +252,7 @@ GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, i
GIT_EXTERN(int) git_reference_delete(git_reference *ref);
/**
- * Pack all the loose references in the repository
+ * Pack all the loose references in the repository.
*
* This method will load into the cache all the loose
* references on the repository and update the
@@ -237,44 +267,42 @@ GIT_EXTERN(int) git_reference_delete(git_reference *ref);
GIT_EXTERN(int) git_reference_packall(git_repository *repo);
/**
- * Fill a list with all the references that can be found
- * in a repository.
+ * Fill a list with all the references that can be found in a repository.
*
- * The listed references may be filtered by type, or using
- * a bitwise OR of several types. Use the magic value
- * `GIT_REF_LISTALL` to obtain all references, including
- * packed ones.
+ * Using the `list_flags` parameter, the listed references may be filtered
+ * by type (`GIT_REF_OID` or `GIT_REF_SYMBOLIC`) or using a bitwise OR of
+ * `git_ref_t` values. To include packed refs, include `GIT_REF_PACKED`.
+ * For convenience, use the value `GIT_REF_LISTALL` to obtain all
+ * references, including packed ones.
*
- * The string array will be filled with the names of all
- * references; these values are owned by the user and
- * should be free'd manually when no longer needed, using
- * `git_strarray_free`.
+ * The string array will be filled with the names of all references; these
+ * values are owned by the user and should be free'd manually when no
+ * longer needed, using `git_strarray_free()`.
*
* @param array Pointer to a git_strarray structure where
* the reference names will be stored
* @param repo Repository where to find the refs
- * @param list_flags Filtering flags for the reference
- * listing.
+ * @param list_flags Filtering flags for the reference listing
* @return 0 or an error code
*/
GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo, unsigned int list_flags);
/**
- * Perform an operation on each reference in the repository
+ * Perform a callback on each reference in the repository.
*
- * The processed references may be filtered by type, or using
- * a bitwise OR of several types. Use the magic value
- * `GIT_REF_LISTALL` to obtain all references, including
- * packed ones.
+ * Using the `list_flags` parameter, the references may be filtered by
+ * type (`GIT_REF_OID` or `GIT_REF_SYMBOLIC`) or using a bitwise OR of
+ * `git_ref_t` values. To include packed refs, include `GIT_REF_PACKED`.
+ * For convenience, use the value `GIT_REF_LISTALL` to obtain all
+ * references, including packed ones.
*
- * The `callback` function will be called for each of the references
- * in the repository, and will receive the name of the reference and
- * the `payload` value passed to this method. Returning a non-zero
- * value from the callback will terminate the iteration.
+ * The `callback` function will be called for each reference in the
+ * repository, receiving the name of the reference and the `payload` value
+ * passed to this method. Returning a non-zero value from the callback
+ * will terminate the iteration.
*
* @param repo Repository where to find the refs
- * @param list_flags Filtering flags for the reference
- * listing.
+ * @param list_flags Filtering flags for the reference listing.
* @param callback Function which will be called for every listed ref
* @param payload Additional data to pass to the callback
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
@@ -282,7 +310,7 @@ GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo, un
GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload);
/**
- * Check if a reference has been loaded from a packfile
+ * Check if a reference has been loaded from a packfile.
*
* @param ref A git reference
* @return 0 in case it's not packed; 1 otherwise
@@ -290,19 +318,17 @@ GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_fl
GIT_EXTERN(int) git_reference_is_packed(git_reference *ref);
/**
- * Reload a reference from disk
+ * Reload a reference from disk.
*
- * Reference pointers may become outdated if the Git
- * repository is accessed simultaneously by other clients
- * while the library is open.
+ * Reference pointers can become outdated if the Git repository is
+ * accessed simultaneously by other clients while the library is open.
*
- * This method forces a reload of the reference from disk,
- * to ensure that the provided information is still
- * reliable.
+ * This method forces a reload of the reference from disk, to ensure that
+ * the provided information is still reliable.
*
- * If the reload fails (e.g. the reference no longer exists
- * on disk, or has become corrupted), an error code will be
- * returned and the reference pointer will be invalidated.
+ * If the reload fails (e.g. the reference no longer exists on disk, or
+ * has become corrupted), an error code will be returned and the reference
+ * pointer will be invalidated and freed.
*
* @param ref The reference to reload
* @return 0 on success, or an error code
@@ -310,7 +336,7 @@ GIT_EXTERN(int) git_reference_is_packed(git_reference *ref);
GIT_EXTERN(int) git_reference_reload(git_reference *ref);
/**
- * Free the given reference
+ * Free the given reference.
*
* @param ref git_reference
*/
@@ -326,36 +352,30 @@ GIT_EXTERN(void) git_reference_free(git_reference *ref);
GIT_EXTERN(int) git_reference_cmp(git_reference *ref1, git_reference *ref2);
/**
- * Loop over all the references and issue a callback for each one
- * which name matches the given glob pattern.
- *
- * The processed references may be filtered by type, or using
- * a bitwise OR of several types. Use the magic value
- * `GIT_REF_LISTALL` to obtain all references, including
- * packed ones.
- *
- * @param repo Repository where to find the references.
+ * Perform a callback on each reference in the repository whose name
+ * matches the given pattern.
*
- * @param glob Glob pattern references should match.
+ * This function acts like `git_reference_foreach()` with an additional
+ * pattern match being applied to the reference name before issuing the
+ * callback function. See that function for more information.
*
- * @param list_flags Filtering flags for the reference
- * listing.
+ * The pattern is matched using fnmatch or "glob" style where a '*' matches
+ * any sequence of letters, a '?' matches any letter, and square brackets
+ * can be used to define character ranges (such as "[0-9]" for digits).
*
- * @param callback Callback to invoke per found reference.
- *
- * @param payload Extra parameter to callback function.
- *
- * @return 0 or an error code.
+ * @param repo Repository where to find the refs
+ * @param glob Pattern to match (fnmatch-style) against reference name.
+ * @param list_flags Filtering flags for the reference listing.
+ * @param callback Function which will be called for every listed ref
+ * @param payload Additional data to pass to the callback
+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_reference_foreach_glob(
- git_repository *repo,
- const char *glob,
- unsigned int list_flags,
- int (*callback)(
- const char *reference_name,
- void *payload),
- void *payload
-);
+ git_repository *repo,
+ const char *glob,
+ unsigned int list_flags,
+ int (*callback)(const char *reference_name, void *payload),
+ void *payload);
/**
* Check if a reflog exists for the specified reference.
@@ -387,7 +407,8 @@ GIT_EXTERN(int) git_reference_is_branch(git_reference *ref);
*/
GIT_EXTERN(int) git_reference_is_remote(git_reference *ref);
-enum {
+
+typedef enum {
GIT_REF_FORMAT_NORMAL = 0,
/**
@@ -406,27 +427,25 @@ enum {
* (e.g., foo/<star>/bar but not foo/bar<star>).
*/
GIT_REF_FORMAT_REFSPEC_PATTERN = (1 << 1),
-};
+} git_reference_normalize_t;
/**
- * Normalize the reference name by removing any leading
- * slash (/) characters and collapsing runs of adjacent slashes
- * between name components into a single slash.
- *
- * Once normalized, if the reference name is valid, it will be
- * returned in the user allocated buffer.
- *
- * @param buffer_out The user allocated buffer where the
- * normalized name will be stored.
- *
- * @param buffer_size buffer_out size
- *
- * @param name name to be checked.
- *
- * @param flags Flags to determine the options to be applied while
- * checking the validatity of the name.
- *
- * @return 0 or an error code.
+ * Normalize reference name and check validity.
+ *
+ * This will normalize the reference name by removing any leading slash
+ * '/' characters and collapsing runs of adjacent slashes between name
+ * components into a single slash.
+ *
+ * Once normalized, if the reference name is valid, it will be returned in
+ * the user allocated buffer.
+ *
+ * @param buffer_out User allocated buffer to store normalized name
+ * @param buffer_size Size of buffer_out
+ * @param name Reference name to be checked.
+ * @param flags Flags to constrain name validation rules - see the
+ * GIT_REF_FORMAT constants above.
+ * @return 0 on success or error code (GIT_EBUFS if buffer is too small, -1
+ * if reference is invalid)
*/
GIT_EXTERN(int) git_reference_normalize_name(
char *buffer_out,
@@ -435,8 +454,7 @@ GIT_EXTERN(int) git_reference_normalize_name(
unsigned int flags);
/**
- * Recursively peel an reference until an object of the
- * specified type is met.
+ * Recursively peel reference until object of the specified type is found.
*
* The retrieved `peeled` object is owned by the repository
* and should be closed with the `git_object_free` method.
@@ -457,12 +475,18 @@ GIT_EXTERN(int) git_reference_peel(
/**
* Ensure the reference name is well-formed.
*
- * @param refname name to be checked.
+ * Valid reference names must follow one of two patterns:
+ *
+ * 1. Top-level names must contain only capital letters and underscores,
+ * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ * 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ * the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ * sequences ".." and "@{" which have special meaning to revparse.
*
+ * @param refname name to be checked.
* @return 1 if the reference name is acceptable; 0 if it isn't
*/
-GIT_EXTERN(int) git_reference_is_valid_name(
- const char *refname);
+GIT_EXTERN(int) git_reference_is_valid_name(const char *refname);
/** @} */
GIT_END_DECL
diff --git a/include/git2/remote.h b/include/git2/remote.h
index 6471acc6a..44390e7a4 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -13,6 +13,7 @@
#include "net.h"
#include "indexer.h"
#include "strarray.h"
+#include "transport.h"
/**
* @file git2/remote.h
@@ -50,7 +51,7 @@ GIT_EXTERN(int) git_remote_new(git_remote **out, git_repository *repo, const cha
* Get the information for a particular remote
*
* @param out pointer to the new remote object
- * @param cfg the repository's configuration
+ * @param repo the associated repository
* @param name the remote's name
* @return 0 or an error code
*/
@@ -167,8 +168,9 @@ GIT_EXTERN(int) git_remote_connect(git_remote *remote, int direction);
* If you a return a non-zero value from the callback, this will stop
* looping over the refs.
*
- * @param refs where to store the refs
* @param remote the remote
+ * @param list_cb function to call with each ref discovered at the remote
+ * @param payload additional data to pass to the callback
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload);
@@ -183,10 +185,16 @@ GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void
* filename will be NULL and the function will return success.
*
* @param remote the remote to download from
- * @param filename where to store the temporary filename
+ * @param progress_cb function to call with progress information. Be aware that
+ * this is called inline with network and indexing operations, so performance
+ * may be affected.
+ * @param progress_payload payload for the progress callback
* @return 0 or an error code
*/
-GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats);
+GIT_EXTERN(int) git_remote_download(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
/**
* Check whether the remote is connected
@@ -194,6 +202,7 @@ GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_in
* Check whether the remote's underlying transport is connected to the
* remote host.
*
+ * @param remote the remote
* @return 1 if it's connected, 0 otherwise.
*/
GIT_EXTERN(int) git_remote_connected(git_remote *remote);
@@ -203,6 +212,8 @@ GIT_EXTERN(int) git_remote_connected(git_remote *remote);
*
* At certain points in its operation, the network code checks whether
* the operation has been cancelled and if so stops the operation.
+ *
+ * @param remote the remote
*/
GIT_EXTERN(void) git_remote_stop(git_remote *remote);
@@ -230,7 +241,7 @@ GIT_EXTERN(void) git_remote_free(git_remote *remote);
* Update the tips to the new state
*
* @param remote the remote to update
- * @param cb callback to run on each ref update. 'a' is the old value, 'b' is then new value
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_remote_update_tips(git_remote *remote);
@@ -268,6 +279,7 @@ GIT_EXTERN(int) git_remote_list(git_strarray *remotes_list, git_repository *repo
* @param repo the repository in which to create the remote
* @param name the remote's name
* @param url the remote's url
+ * @return 0 or an error code
*/
GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url);
@@ -277,10 +289,39 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha
* @param remote the remote to configure
* @param check whether to check the server's certificate (defaults to yes)
*/
-
GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check);
/**
+ * Set a credentials acquisition callback for this remote. If the remote is
+ * not available for anonymous access, then you must set this callback in order
+ * to provide credentials to the transport at the time of authentication
+ * failure so that retry can be performed.
+ *
+ * @param remote the remote to configure
+ * @param cred_acquire_cb The credentials acquisition callback to use (defaults
+ * to NULL)
+ */
+GIT_EXTERN(void) git_remote_set_cred_acquire_cb(
+ git_remote *remote,
+ git_cred_acquire_cb cred_acquire_cb);
+
+/**
+ * Sets a custom transport for the remote. The caller can use this function
+ * to bypass the automatic discovery of a transport by URL scheme (i.e.
+ * http://, https://, git://) and supply their own transport to be used
+ * instead. After providing the transport to a remote using this function,
+ * the transport object belongs exclusively to that remote, and the remote will
+ * free it when it is freed with git_remote_free.
+ *
+ * @param remote the remote to configure
+ * @param transport the transport object for the remote to use
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_set_transport(
+ git_remote *remote,
+ git_transport *transport);
+
+/**
* Argument to the completion callback which tells it which operation
* finished.
*/
@@ -313,6 +354,11 @@ struct git_remote_callbacks {
*/
GIT_EXTERN(void) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks);
+/**
+ * Get the statistics structure that is filled in by the fetch operation.
+ */
+GIT_EXTERN(const git_transfer_progress *) git_remote_stats(git_remote *remote);
+
enum {
GIT_REMOTE_DOWNLOAD_TAGS_UNSET,
GIT_REMOTE_DOWNLOAD_TAGS_NONE,
@@ -336,6 +382,41 @@ GIT_EXTERN(int) git_remote_autotag(git_remote *remote);
*/
GIT_EXTERN(void) git_remote_set_autotag(git_remote *remote, int value);
+/**
+ * Give the remote a new name
+ *
+ * All remote-tracking branches and configuration settings
+ * for the remote are updated.
+ *
+ * @param remote the remote to rename
+ * @param new_name the new name the remote should bear
+ * @param callback Optional callback to notify the consumer of fetch refspecs
+ * that haven't been automatically updated and need potential manual tweaking.
+ * @param payload Additional data to pass to the callback
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_remote_rename(
+ git_remote *remote,
+ const char *new_name,
+ int (*callback)(const char *problematic_refspec, void *payload),
+ void *payload);
+
+/**
+ * Retrieve the update FETCH_HEAD setting.
+ *
+ * @param remote the remote to query
+ * @return the update FETCH_HEAD setting
+ */
+GIT_EXTERN(int) git_remote_update_fetchhead(git_remote *remote);
+
+/**
+ * Sets the update FETCH_HEAD setting. By default, FETCH_HEAD will be
+ * updated on every fetch. Set to 0 to disable.
+ *
+ * @param remote the remote to configure
+ * @param value 0 to disable updating FETCH_HEAD
+ */
+GIT_EXTERN(void) git_remote_set_update_fetchhead(git_remote *remote, int value);
/** @} */
GIT_END_DECL
diff --git a/include/git2/repository.h b/include/git2/repository.h
index 193ac9523..f891e91e9 100644
--- a/include/git2/repository.h
+++ b/include/git2/repository.h
@@ -273,7 +273,7 @@ GIT_EXTERN(int) git_repository_init_ext(
* @param repo a repository object
*
* @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing
- * branch, an error code otherwise
+ * branch, GIT_ENOTFOUND when HEAD is missing; an error code otherwise
*/
GIT_EXTERN(int) git_repository_head(git_reference **head_out, git_repository *repo);
@@ -305,7 +305,7 @@ GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo);
* Check if a repository is empty
*
* An empty repository has just been initialized and contains
- * no commits.
+ * no references.
*
* @param repo Repo to test
* @return 1 if the repository is empty, 0 if it isn't, error code
@@ -569,6 +569,28 @@ GIT_EXTERN(int) git_repository_set_head_detached(
GIT_EXTERN(int) git_repository_detach_head(
git_repository* repo);
+typedef enum {
+ GIT_REPOSITORY_STATE_NONE,
+ GIT_REPOSITORY_STATE_MERGE,
+ GIT_REPOSITORY_STATE_REVERT,
+ GIT_REPOSITORY_STATE_CHERRY_PICK,
+ GIT_REPOSITORY_STATE_BISECT,
+ GIT_REPOSITORY_STATE_REBASE,
+ GIT_REPOSITORY_STATE_REBASE_INTERACTIVE,
+ GIT_REPOSITORY_STATE_REBASE_MERGE,
+ GIT_REPOSITORY_STATE_APPLY_MAILBOX,
+ GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE,
+} git_repository_state_t;
+
+/**
+ * Determines the status of a git repository - ie, whether an operation
+ * (merge, cherry-pick, etc) is in progress.
+ *
+ * @param repo Repository pointer
+ * @return The state of the repository
+ */
+GIT_EXTERN(int) git_repository_state(git_repository *repo);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/include/git2/stash.h b/include/git2/stash.h
new file mode 100644
index 000000000..3ecd9e88d
--- /dev/null
+++ b/include/git2/stash.h
@@ -0,0 +1,122 @@
+/*
+ * 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_stash_h__
+#define INCLUDE_git_stash_h__
+
+#include "common.h"
+#include "types.h"
+
+/**
+ * @file git2/stash.h
+ * @brief Git stash management routines
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+enum {
+ GIT_STASH_DEFAULT = 0,
+
+ /* All changes already added to the index
+ * are left intact in the working directory
+ */
+ GIT_STASH_KEEP_INDEX = (1 << 0),
+
+ /* All untracked files are also stashed and then
+ * cleaned up from the working directory
+ */
+ GIT_STASH_INCLUDE_UNTRACKED = (1 << 1),
+
+ /* All ignored files are also stashed and then
+ * cleaned up from the working directory
+ */
+ GIT_STASH_INCLUDE_IGNORED = (1 << 2),
+};
+
+/**
+ * Save the local modifications to a new stash.
+ *
+ * @param out Object id of the commit containing the stashed state.
+ * This commit is also the target of the direct reference refs/stash.
+ *
+ * @param repo The owning repository.
+ *
+ * @param stasher The identity of the person performing the stashing.
+ *
+ * @param message Optional description along with the stashed state.
+ *
+ * @param flags Flags to control the stashing process.
+ *
+ * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash,
+ * or error code.
+ */
+
+GIT_EXTERN(int) git_stash_save(
+ git_oid *out,
+ git_repository *repo,
+ git_signature *stasher,
+ const char *message,
+ uint32_t flags);
+
+/**
+ * When iterating over all the stashed states, callback that will be
+ * issued per entry.
+ *
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ *
+ * @param message The stash message.
+ *
+ * @param stash_oid The commit oid of the stashed state.
+ *
+ * @param payload Extra parameter to callback function.
+ *
+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code
+ */
+typedef int (*stash_cb)(
+ size_t index,
+ const char* message,
+ const git_oid *stash_oid,
+ void *payload);
+
+/**
+ * Loop over all the stashed states and issue a callback for each one.
+ *
+ * If the callback returns a non-zero value, this will stop looping.
+ *
+ * @param repo Repository where to find the stash.
+ *
+ * @param callabck Callback to invoke per found stashed state. The most recent
+ * stash state will be enumerated first.
+ *
+ * @param payload Extra parameter to callback function.
+ *
+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code
+ */
+GIT_EXTERN(int) git_stash_foreach(
+ git_repository *repo,
+ stash_cb callback,
+ void *payload);
+
+/**
+ * Remove a single stashed state from the stash list.
+ *
+ * @param repo The owning repository.
+ *
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ *
+ * @return 0 on success, or error code
+ */
+
+GIT_EXTERN(int) git_stash_drop(
+ git_repository *repo,
+ size_t index);
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/status.h b/include/git2/status.h
index 979e6e4ff..8c59d768d 100644
--- a/include/git2/status.h
+++ b/include/git2/status.h
@@ -19,6 +19,16 @@
*/
GIT_BEGIN_DECL
+/**
+ * Status flags for a single file.
+ *
+ * A combination of these values will be returned to indicate the status of
+ * a file. Status compares the working directory, the index, and the
+ * current HEAD of the repository. The `GIT_STATUS_INDEX` set of flags
+ * represents the status of file in the index relative to the HEAD, and the
+ * `GIT_STATUS_WT` set of flags represent the status of the file in the
+ * working directory relative to the index.
+ */
typedef enum {
GIT_STATUS_CURRENT = 0,
@@ -39,12 +49,16 @@ typedef enum {
/**
* Gather file statuses and run a callback for each one.
*
- * The callback is passed the path of the file, the status and the data
- * pointer passed to this function. If the callback returns something other
- * than 0, this function will stop looping and return GIT_EUSER.
+ * The callback is passed the path of the file, the status (a combination of
+ * the `git_status_t` values above) and the `payload` data pointer passed
+ * into this function.
+ *
+ * If the callback returns a non-zero value, this function will stop looping
+ * and return GIT_EUSER.
*
- * @param repo a repository object
- * @param callback the function to call on each file
+ * @param repo A repository object
+ * @param callback The function to call on each file
+ * @param payload Pointer to pass through to callback function
* @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_status_foreach(
@@ -53,7 +67,7 @@ GIT_EXTERN(int) git_status_foreach(
void *payload);
/**
- * Select the files on which to report status.
+ * For extended status, select the files on which to report status.
*
* - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the
* rough equivalent of `git status --porcelain` where each file
@@ -81,40 +95,55 @@ typedef enum {
/**
* Flags to control status callbacks
*
- * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should
- * be made on untracked files. These will only be made if the
- * workdir files are included in the status "show" option.
- * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should
- * get callbacks. Again, these callbacks will only be made if
- * the workdir files are included in the status "show" option.
- * Right now, there is no option to include all files in
- * directories that are ignored completely.
- * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback
- * should be made even on unmodified files.
- * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories
- * which appear to be submodules should just be skipped over.
- * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the
- * contents of untracked directories should be included in the
- * status. Normally if an entire directory is new, then just
- * the top-level directory will be included (with a trailing
- * slash on the entry name). Given this flag, the directory
- * itself will not be included, but all the files in it will.
- * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given
- * path will be treated as a literal path, and not as a pathspec.
+ * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made
+ * on untracked files. These will only be made if the workdir files are
+ * included in the status "show" option.
+ * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should get
+ * callbacks. Again, these callbacks will only be made if the workdir
+ * files are included in the status "show" option. Right now, there is
+ * no option to include all files in directories that are ignored
+ * completely.
+ * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be
+ * made even on unmodified files.
+ * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories which
+ * appear to be submodules should just be skipped over.
+ * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the contents of
+ * untracked directories should be included in the status. Normally if
+ * an entire directory is new, then just the top-level directory will be
+ * included (with a trailing slash on the entry name). Given this flag,
+ * the directory itself will not be included, but all the files in it
+ * will.
+ * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path
+ * will be treated as a literal path, and not as a pathspec.
+ *
+ * Calling `git_status_foreach()` is like calling the extended version
+ * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
+ * and GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS.
*/
-
-enum {
+typedef enum {
GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0),
GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1),
GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),
GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3),
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1 << 5),
-};
+} git_status_opt_t;
/**
- * Options to control how callbacks will be made by
- * `git_status_foreach_ext()`.
+ * Options to control how `git_status_foreach_ext()` will issue callbacks.
+ *
+ * This structure is set so that zeroing it out will give you relatively
+ * sane defaults.
+ *
+ * The `show` value is one of the `git_status_show_t` constants that
+ * control which files to scan and in what order.
+ *
+ * The `flags` value is an OR'ed combination of the `git_status_opt_t`
+ * values above.
+ *
+ * The `pathspec` is an array of path patterns to match (using
+ * fnmatch-style matching), or just an array of paths to match exactly if
+ * `GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH` is specified in the flags.
*/
typedef struct {
git_status_show_t show;
@@ -124,6 +153,17 @@ typedef struct {
/**
* Gather file status information and run callbacks as requested.
+ *
+ * This is an extended version of the `git_status_foreach()` API that
+ * allows for more granular control over which paths will be processed and
+ * in what order. See the `git_status_options` structure for details
+ * about the additional controls that this makes available.
+ *
+ * @param repo Repository object
+ * @param opts Status options structure
+ * @param callback The function to call on each file
+ * @param payload Pointer to pass through to callback function
+ * @return 0 on success, GIT_EUSER on non-zero callback, or error code
*/
GIT_EXTERN(int) git_status_foreach_ext(
git_repository *repo,
@@ -132,14 +172,17 @@ GIT_EXTERN(int) git_status_foreach_ext(
void *payload);
/**
- * Get file status for a single file
- *
- * @param status_flags the status value
- * @param repo a repository object
- * @param path the file to retrieve status for, rooted at the repo's workdir
- * @return GIT_EINVALIDPATH when `path` points at a folder, GIT_ENOTFOUND when
- * the file doesn't exist in any of HEAD, the index or the worktree,
- * 0 otherwise
+ * Get file status for a single file.
+ *
+ * This is not quite the same as calling `git_status_foreach_ext()` with
+ * the pathspec set to the specified path.
+ *
+ * @param status_flags The status value for the file
+ * @param repo A repository object
+ * @param path The file to retrieve status for, rooted at the repo's workdir
+ * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD,
+ * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder,
+ * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error.
*/
GIT_EXTERN(int) git_status_file(
unsigned int *status_flags,
@@ -156,9 +199,9 @@ GIT_EXTERN(int) git_status_file(
* One way to think of this is if you were to do "git add ." on the
* directory containing the file, would it be added or not?
*
- * @param ignored boolean returning 0 if the file is not ignored, 1 if it is
- * @param repo a repository object
- * @param path the file to check ignores for, rooted at the repo's workdir.
+ * @param ignored Boolean returning 0 if the file is not ignored, 1 if it is
+ * @param repo A repository object
+ * @param path The file to check ignores for, rooted at the repo's workdir.
* @return 0 if ignore rules could be processed for the file (regardless
* of whether it exists or not), or an error < 0 if they could not.
*/
diff --git a/include/git2/tag.h b/include/git2/tag.h
index 5602914f7..a1b685f12 100644
--- a/include/git2/tag.h
+++ b/include/git2/tag.h
@@ -104,7 +104,7 @@ GIT_EXTERN(const git_oid *) git_tag_target_oid(git_tag *tag);
* @param tag a previously loaded tag.
* @return type of the tagged object
*/
-GIT_EXTERN(git_otype) git_tag_type(git_tag *tag);
+GIT_EXTERN(git_otype) git_tag_target_type(git_tag *tag);
/**
* Get the name of a tag
diff --git a/include/git2/threads.h b/include/git2/threads.h
index 567a10487..f448f6a4d 100644
--- a/include/git2/threads.h
+++ b/include/git2/threads.h
@@ -27,8 +27,10 @@ GIT_BEGIN_DECL
*
* If libgit2 has been built without GIT_THREADS
* support, this function is a no-op.
+ *
+ * @return 0 or an error code
*/
-GIT_EXTERN(void) git_threads_init(void);
+GIT_EXTERN(int) git_threads_init(void);
/**
* Shutdown the threading system.
diff --git a/include/git2/transport.h b/include/git2/transport.h
new file mode 100644
index 000000000..b2bdaae71
--- /dev/null
+++ b/include/git2/transport.h
@@ -0,0 +1,306 @@
+/*
+ * 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_transport_h__
+#define INCLUDE_git_transport_h__
+
+#include "indexer.h"
+#include "net.h"
+
+/**
+ * @file git2/transport.h
+ * @brief Git transport interfaces and functions
+ * @defgroup git_transport interfaces and functions
+ * @ingroup Git
+ * @{
+ */
+GIT_BEGIN_DECL
+
+/*
+ *** Begin interface for credentials acquisition ***
+ */
+
+typedef enum {
+ /* git_cred_userpass_plaintext */
+ GIT_CREDTYPE_USERPASS_PLAINTEXT = 1,
+} git_credtype_t;
+
+/* The base structure for all credential types */
+typedef struct git_cred {
+ git_credtype_t credtype;
+ void (*free)(
+ struct git_cred *cred);
+} git_cred;
+
+/* A plaintext username and password */
+typedef struct git_cred_userpass_plaintext {
+ git_cred parent;
+ char *username;
+ char *password;
+} git_cred_userpass_plaintext;
+
+/**
+ * Creates a new plain-text username and password credential object.
+ *
+ * @param cred The newly created credential object.
+ * @param username The username of the credential.
+ * @param password The password of the credential.
+ */
+GIT_EXTERN(int) git_cred_userpass_plaintext_new(
+ git_cred **cred,
+ const char *username,
+ const char *password);
+
+/**
+ * Signature of a function which acquires a credential object.
+ *
+ * @param cred The newly created credential object.
+ * @param url The resource for which we are demanding a credential.
+ * @param allowed_types A bitmask stating which cred types are OK to return.
+ */
+typedef int (*git_cred_acquire_cb)(
+ git_cred **cred,
+ const char *url,
+ int allowed_types);
+
+/*
+ *** End interface for credentials acquisition ***
+ *** Begin base transport interface ***
+ */
+
+typedef enum {
+ GIT_TRANSPORTFLAGS_NONE = 0,
+ /* If the connection is secured with SSL/TLS, the authenticity
+ * of the server certificate should not be verified. */
+ GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1
+} git_transport_flags_t;
+
+typedef void (*git_transport_message_cb)(const char *str, int len, void *data);
+
+typedef struct git_transport {
+ /* Set progress and error callbacks */
+ int (*set_callbacks)(struct git_transport *transport,
+ git_transport_message_cb progress_cb,
+ git_transport_message_cb error_cb,
+ void *payload);
+
+ /* Connect the transport to the remote repository, using the given
+ * direction. */
+ int (*connect)(struct git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ int direction,
+ int flags);
+
+ /* This function may be called after a successful call to connect(). The
+ * provided callback is invoked for each ref discovered on the remote
+ * end. */
+ int (*ls)(struct git_transport *transport,
+ git_headlist_cb list_cb,
+ void *payload);
+
+ /* Reserved until push is implemented. */
+ int (*push)(struct git_transport *transport);
+
+ /* This function may be called after a successful call to connect(), when
+ * the direction is FETCH. The function performs a negotiation to calculate
+ * the wants list for the fetch. */
+ int (*negotiate_fetch)(struct git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count);
+
+ /* This function may be called after a successful call to negotiate_fetch(),
+ * when the direction is FETCH. This function retrieves the pack file for
+ * the fetch from the remote end. */
+ int (*download_pack)(struct git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
+
+ /* Checks to see if the transport is connected */
+ int (*is_connected)(struct git_transport *transport, int *connected);
+
+ /* Reads the flags value previously passed into connect() */
+ int (*read_flags)(struct git_transport *transport, int *flags);
+
+ /* Cancels any outstanding transport operation */
+ void (*cancel)(struct git_transport *transport);
+
+ /* This function is the reverse of connect() -- it terminates the
+ * connection to the remote end. */
+ int (*close)(struct git_transport *transport);
+
+ /* Frees/destructs the git_transport object. */
+ void (*free)(struct git_transport *transport);
+} git_transport;
+
+/**
+ * Function to use to create a transport from a URL. The transport database
+ * is scanned to find a transport that implements the scheme of the URI (i.e.
+ * git:// or http://) and a transport object is returned to the caller.
+ *
+ * @param transport The newly created transport (out)
+ * @param url The URL to connect to
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_new(git_transport **transport, const char *url);
+
+/**
+ * Function which checks to see if a transport could be created for the
+ * given URL (i.e. checks to see if libgit2 has a transport that supports
+ * the given URL's scheme)
+ *
+ * @param url The URL to check
+ * @return Zero if the URL is not valid; nonzero otherwise
+ */
+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 **transport, void *param);
+
+/* Transports which come with libgit2 (match git_transport_cb). The expected
+ * value for "param" is listed in-line below. */
+
+/**
+ * Create an instance of the dummy transport.
+ *
+ * @param transport The newly created transport (out)
+ * @param param You must pass NULL for this parameter.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_dummy(
+ git_transport **transport,
+ /* NULL */ void *param);
+
+/**
+ * Create an instance of the local transport.
+ *
+ * @param transport The newly created transport (out)
+ * @param param You must pass NULL for this parameter.
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_local(
+ git_transport **transport,
+ /* NULL */ void *param);
+
+/**
+ * Create an instance of the smart transport.
+ *
+ * @param transport The newly created transport (out)
+ * @param param A pointer to a git_smart_subtransport_definition
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_smart(
+ git_transport **transport,
+ /* (git_smart_subtransport_definition *) */ void *param);
+
+/*
+ *** End of base transport interface ***
+ *** Begin interface for subtransports for the smart transport ***
+ */
+
+/* The smart transport knows how to speak the git protocol, but it has no
+ * knowledge of how to establish a connection between it and another endpoint,
+ * or how to move data back and forth. For this, a subtransport interface is
+ * declared, and the smart transport delegates this work to the subtransports.
+ * Three subtransports are implemented: git, http, and winhttp. (The http and
+ * winhttp transports each implement both http and https.) */
+
+/* Subtransports can either be RPC = 0 (persistent connection) or RPC = 1
+ * (request/response). The smart transport handles the differences in its own
+ * logic. The git subtransport is RPC = 0, while http and winhttp are both
+ * RPC = 1. */
+
+/* Actions that the smart transport can ask
+ * a subtransport to perform */
+typedef enum {
+ GIT_SERVICE_UPLOADPACK_LS = 1,
+ GIT_SERVICE_UPLOADPACK = 2,
+} git_smart_service_t;
+
+struct git_smart_subtransport;
+
+/* A stream used by the smart transport to read and write data
+ * from a subtransport */
+typedef struct git_smart_subtransport_stream {
+ /* The owning subtransport */
+ struct git_smart_subtransport *subtransport;
+
+ int (*read)(
+ struct git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read);
+
+ int (*write)(
+ struct git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len);
+
+ void (*free)(
+ struct git_smart_subtransport_stream *stream);
+} git_smart_subtransport_stream;
+
+/* An implementation of a subtransport which carries data for the
+ * smart transport */
+typedef struct git_smart_subtransport {
+ int (* action)(
+ git_smart_subtransport_stream **out,
+ struct git_smart_subtransport *transport,
+ const char *url,
+ git_smart_service_t action);
+
+ void (* free)(struct git_smart_subtransport *transport);
+} git_smart_subtransport;
+
+/* A function which creates a new subtransport for the smart transport */
+typedef int (*git_smart_subtransport_cb)(
+ git_smart_subtransport **out,
+ git_transport* owner);
+
+typedef struct git_smart_subtransport_definition {
+ /* The function to use to create the git_smart_subtransport */
+ git_smart_subtransport_cb callback;
+ /* True if the protocol is stateless; false otherwise. For example,
+ * http:// is stateless, but git:// is not. */
+ unsigned rpc : 1;
+} git_smart_subtransport_definition;
+
+/* Smart transport subtransports that come with libgit2 */
+
+/**
+ * Create an instance of the http subtransport. This subtransport
+ * also supports https. On Win32, this subtransport may be implemented
+ * using the WinHTTP library.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_http(
+ git_smart_subtransport **out,
+ git_transport* owner);
+
+/**
+ * Create an instance of the git subtransport.
+ *
+ * @param out The newly created subtransport
+ * @param owner The smart transport to own this subtransport
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_smart_subtransport_git(
+ git_smart_subtransport **out,
+ git_transport* owner);
+
+/*
+ *** End interface for subtransports for the smart transport ***
+ */
+
+/** @} */
+GIT_END_DECL
+#endif
diff --git a/include/git2/tree.h b/include/git2/tree.h
index 2ee1f4afa..527f81819 100644
--- a/include/git2/tree.h
+++ b/include/git2/tree.h
@@ -185,24 +185,6 @@ GIT_EXTERN(int) git_tree_entry_to_object(
const git_tree_entry *entry);
/**
- * Write a tree to the ODB from the index file
- *
- * This method will scan the index and write a representation
- * of its current state back to disk; it recursively creates
- * tree objects for each of the subtrees stored in the index,
- * but only returns the OID of the root tree. This is the OID
- * that can be used e.g. to create a commit.
- *
- * The index instance cannot be bare, and needs to be associated
- * to an existing repository.
- *
- * @param oid Pointer where to store the written tree
- * @param index Index to write
- * @return 0 or an error code
- */
-GIT_EXTERN(int) git_tree_create_fromindex(git_oid *oid, git_index *index);
-
-/**
* Create a new tree builder.
*
* The tree builder can be used to create or modify
diff --git a/include/git2/types.h b/include/git2/types.h
index 01ddbf3d6..58cbaecc5 100644
--- a/include/git2/types.h
+++ b/include/git2/types.h
@@ -89,6 +89,9 @@ typedef struct git_odb_object git_odb_object;
/** A stream to read/write from the ODB */
typedef struct git_odb_stream git_odb_stream;
+/** A stream to write a packfile to the ODB */
+typedef struct git_odb_writepack git_odb_writepack;
+
/**
* Representation of an existing git repository,
* including all its object contents
diff --git a/src/attr.c b/src/attr.c
index 025ad3c87..b5757446f 100644
--- a/src/attr.c
+++ b/src/attr.c
@@ -261,32 +261,26 @@ bool git_attr_cache__is_cached(
static int load_attr_file(
const char **data,
- git_attr_file_stat_sig *sig,
+ git_futils_filestamp *stamp,
const char *filename)
{
int error;
git_buf content = GIT_BUF_INIT;
- struct stat st;
- if (p_stat(filename, &st) < 0)
- return GIT_ENOTFOUND;
+ error = git_futils_filestamp_check(stamp, filename);
+ if (error < 0)
+ return error;
- if (sig != NULL &&
- (git_time_t)st.st_mtime == sig->seconds &&
- (git_off_t)st.st_size == sig->size &&
- (unsigned int)st.st_ino == sig->ino)
+ /* if error == 0, then file is up to date. By returning GIT_ENOTFOUND,
+ * we tell the caller not to reparse this file...
+ */
+ if (!error)
return GIT_ENOTFOUND;
- error = git_futils_readbuffer_updated(&content, filename, NULL, NULL);
+ error = git_futils_readbuffer(&content, filename);
if (error < 0)
return error;
- if (sig != NULL) {
- sig->seconds = (git_time_t)st.st_mtime;
- sig->size = (git_off_t)st.st_size;
- sig->ino = (unsigned int)st.st_ino;
- }
-
*data = git_buf_detach(&content);
return 0;
@@ -307,7 +301,7 @@ static int load_attr_blob_from_index(
(error = git_index_find(index, relfile)) < 0)
return error;
- entry = git_index_get(index, error);
+ entry = git_index_get_byindex(index, error);
if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0)
return GIT_ENOTFOUND;
@@ -386,7 +380,7 @@ int git_attr_cache__push_file(
git_attr_cache *cache = git_repository_attr_cache(repo);
git_attr_file *file = NULL;
git_blob *blob = NULL;
- git_attr_file_stat_sig st;
+ git_futils_filestamp stamp;
assert(filename && stack);
@@ -408,12 +402,10 @@ int git_attr_cache__push_file(
/* if not in cache, load data, parse, and cache */
if (source == GIT_ATTR_FILE_FROM_FILE) {
- if (file)
- memcpy(&st, &file->cache_data.st, sizeof(st));
- else
- memset(&st, 0, sizeof(st));
+ git_futils_filestamp_set(
+ &stamp, file ? &file->cache_data.stamp : NULL);
- error = load_attr_file(&content, &st, filename);
+ error = load_attr_file(&content, &stamp, filename);
} else {
error = load_attr_blob_from_index(&content, &blob,
repo, file ? &file->cache_data.oid : NULL, relfile);
@@ -448,7 +440,7 @@ int git_attr_cache__push_file(
if (blob)
git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob));
else
- memcpy(&file->cache_data.st, &st, sizeof(st));
+ git_futils_filestamp_set(&file->cache_data.stamp, &stamp);
finish:
/* push file onto vector if we found one*/
diff --git a/src/attr_file.h b/src/attr_file.h
index 877daf306..5bdfc7054 100644
--- a/src/attr_file.h
+++ b/src/attr_file.h
@@ -7,10 +7,12 @@
#ifndef INCLUDE_attr_file_h__
#define INCLUDE_attr_file_h__
+#include "git2/oid.h"
#include "git2/attr.h"
#include "vector.h"
#include "pool.h"
#include "buffer.h"
+#include "fileops.h"
#define GIT_ATTR_FILE ".gitattributes"
#define GIT_ATTR_FILE_INREPO "info/attributes"
@@ -54,19 +56,13 @@ typedef struct {
} git_attr_assignment;
typedef struct {
- git_time_t seconds;
- git_off_t size;
- unsigned int ino;
-} git_attr_file_stat_sig;
-
-typedef struct {
char *key; /* cache "source#path" this was loaded from */
git_vector rules; /* vector of <rule*> or <fnmatch*> */
git_pool *pool;
bool pool_is_allocated;
union {
git_oid oid;
- git_attr_file_stat_sig st;
+ git_futils_filestamp stamp;
} cache_data;
} git_attr_file;
diff --git a/src/branch.c b/src/branch.c
index 991314508..c6173caca 100644
--- a/src/branch.c
+++ b/src/branch.c
@@ -92,6 +92,8 @@ cleanup:
int git_branch_delete(git_reference *branch)
{
int is_head;
+ git_buf config_section = GIT_BUF_INIT;
+ int error = -1;
assert(branch);
@@ -110,7 +112,23 @@ int git_branch_delete(git_reference *branch)
return -1;
}
- return git_reference_delete(branch);
+ if (git_buf_printf(&config_section, "branch.%s", git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto on_error;
+
+ if (git_config_rename_section(
+ git_reference_owner(branch),
+ git_buf_cstr(&config_section),
+ NULL) < 0)
+ goto on_error;
+
+ if (git_reference_delete(branch) < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ git_buf_free(&config_section);
+ return error;
}
typedef struct {
@@ -161,7 +179,9 @@ int git_branch_move(
const char *new_branch_name,
int force)
{
- git_buf new_reference_name = GIT_BUF_INIT;
+ git_buf new_reference_name = GIT_BUF_INIT,
+ old_config_section = GIT_BUF_INIT,
+ new_config_section = GIT_BUF_INIT;
int error;
assert(branch && new_branch_name);
@@ -172,10 +192,28 @@ int git_branch_move(
if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
goto cleanup;
- error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force);
+ if (git_buf_printf(
+ &old_config_section,
+ "branch.%s",
+ git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
+ goto cleanup;
+
+ if ((error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force)) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&new_config_section, "branch.%s", new_branch_name) < 0)
+ goto cleanup;
+
+ if ((error = git_config_rename_section(
+ git_reference_owner(branch),
+ git_buf_cstr(&old_config_section),
+ git_buf_cstr(&new_config_section))) < 0)
+ goto cleanup;
cleanup:
git_buf_free(&new_reference_name);
+ git_buf_free(&old_config_section);
+ git_buf_free(&new_config_section);
return error;
}
@@ -230,6 +268,11 @@ int git_branch_tracking(
if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0)
goto cleanup;
+
+ if (remote_name == NULL || merge_name == NULL) {
+ error = GIT_ENOTFOUND;
+ goto cleanup;
+ }
if (strcmp(".", remote_name) != 0) {
if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0)
@@ -274,7 +317,7 @@ int git_branch_is_head(
error = git_repository_head(&head, git_reference_owner(branch));
- if (error == GIT_EORPHANEDHEAD)
+ if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND)
return false;
if (error < 0)
diff --git a/src/buffer.c b/src/buffer.c
index b40b16b66..e55b0a230 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -549,3 +549,31 @@ void git_buf_unescape(git_buf *buf)
{
buf->size = git__unescape(buf->ptr);
}
+
+int git_buf_splice(
+ git_buf *buf,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert)
+{
+ assert(buf &&
+ where <= git_buf_len(buf) &&
+ where + nb_to_remove <= git_buf_len(buf));
+
+ /* Ported from git.git
+ * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176
+ */
+ if (git_buf_grow(buf, git_buf_len(buf) + nb_to_insert - nb_to_remove) < 0)
+ return -1;
+
+ memmove(buf->ptr + where + nb_to_insert,
+ buf->ptr + where + nb_to_remove,
+ buf->size - where - nb_to_remove);
+
+ memcpy(buf->ptr + where, data, nb_to_insert);
+
+ buf->size = buf->size + nb_to_insert - nb_to_remove;
+ buf->ptr[buf->size] = '\0';
+ return 0;
+}
diff --git a/src/buffer.h b/src/buffer.h
index 2aae06c7c..a2896d486 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -158,4 +158,29 @@ void git_buf_unescape(git_buf *buf);
/* Write data as base64 encoded in buffer */
int git_buf_put_base64(git_buf *buf, const char *data, size_t len);
+/*
+ * Insert, remove or replace a portion of the buffer.
+ *
+ * @param buf The buffer to work with
+ *
+ * @param where The location in the buffer where the transformation
+ * should be applied.
+ *
+ * @param nb_to_remove The number of chars to be removed. 0 to not
+ * remove any character in the buffer.
+ *
+ * @param data A pointer to the data which should be inserted.
+ *
+ * @param nb_to_insert The number of chars to be inserted. 0 to not
+ * insert any character from the buffer.
+ *
+ * @return 0 or an error code.
+ */
+int git_buf_splice(
+ git_buf *buf,
+ size_t where,
+ size_t nb_to_remove,
+ const char *data,
+ size_t nb_to_insert);
+
#endif
diff --git a/src/cache.c b/src/cache.c
index 1f5b8872c..edd3a47dd 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -41,6 +41,7 @@ void git_cache_free(git_cache *cache)
git_cached_obj_decref(cache->nodes[i], cache->free_obj);
}
+ git_mutex_free(&cache->lock);
git__free(cache->nodes);
}
diff --git a/src/checkout.c b/src/checkout.c
index b56b459d2..eff14813d 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -21,19 +21,20 @@
#include "repository.h"
#include "filter.h"
#include "blob.h"
+#include "diff.h"
+#include "pathspec.h"
-struct checkout_diff_data
-{
+typedef struct {
+ git_repository *repo;
+ git_diff_list *diff;
+ git_checkout_opts *opts;
git_buf *path;
size_t workdir_len;
- git_checkout_opts *checkout_opts;
- git_indexer_stats *stats;
- git_repository *owner;
bool can_symlink;
- bool found_submodules;
- bool create_submodules;
int error;
-};
+ size_t total_steps;
+ size_t completed_steps;
+} checkout_diff_data;
static int buffer_to_file(
git_buf *buffer,
@@ -42,20 +43,23 @@ static int buffer_to_file(
int file_open_flags,
mode_t file_mode)
{
- int fd, error, error_close;
+ int fd, error;
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
return error;
- if ((fd = p_open(path, file_open_flags, file_mode)) < 0)
+ if ((fd = p_open(path, file_open_flags, file_mode)) < 0) {
+ giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
return fd;
+ }
- error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer));
-
- error_close = p_close(fd);
-
- if (!error)
- error = error_close;
+ if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) {
+ giterr_set(GITERR_OS, "Could not write to '%s'", path);
+ (void)p_close(fd);
+ } else {
+ if ((error = p_close(fd)) < 0)
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+ }
if (!error &&
(file_mode & 0100) != 0 &&
@@ -107,7 +111,8 @@ static int blob_content_to_file(
if (!file_mode)
file_mode = entry_filemode;
- error = buffer_to_file(&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
+ error = buffer_to_file(
+ &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
cleanup:
git_filters_free(&filters);
@@ -118,7 +123,8 @@ cleanup:
return error;
}
-static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink)
+static int blob_content_to_link(
+ git_blob *blob, const char *path, bool can_symlink)
{
git_buf linktarget = GIT_BUF_INIT;
int error;
@@ -137,39 +143,52 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli
}
static int checkout_submodule(
- struct checkout_diff_data *data,
+ checkout_diff_data *data,
const git_diff_file *file)
{
+ /* Until submodules are supported, UPDATE_ONLY means do nothing here */
+ if ((data->opts->checkout_strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ return 0;
+
if (git_futils_mkdir(
- file->path, git_repository_workdir(data->owner),
- data->checkout_opts->dir_mode, GIT_MKDIR_PATH) < 0)
+ file->path, git_repository_workdir(data->repo),
+ data->opts->dir_mode, GIT_MKDIR_PATH) < 0)
return -1;
- /* TODO: two cases:
+ /* TODO: Support checkout_strategy options. Two circumstances:
* 1 - submodule already checked out, but we need to move the HEAD
* to the new OID, or
* 2 - submodule not checked out and we should recursively check it out
*
- * Checkout will not execute a pull request on the submodule, but a
- * clone command should probably be able to. Do we need a submodule
- * callback option?
+ * Checkout will not execute a pull on the submodule, but a clone
+ * command should probably be able to. Do we need a submodule callback?
*/
return 0;
}
+static void report_progress(
+ checkout_diff_data *data,
+ const char *path)
+{
+ if (data->opts->progress_cb)
+ data->opts->progress_cb(
+ path, data->completed_steps, data->total_steps,
+ data->opts->progress_payload);
+}
+
static int checkout_blob(
- struct checkout_diff_data *data,
+ checkout_diff_data *data,
const git_diff_file *file)
{
+ int error = 0;
git_blob *blob;
- int error;
git_buf_truncate(data->path, data->workdir_len);
- if (git_buf_joinpath(data->path, git_buf_cstr(data->path), file->path) < 0)
+ if (git_buf_puts(data->path, file->path) < 0)
return -1;
- if ((error = git_blob_lookup(&blob, data->owner, &file->oid)) < 0)
+ if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)
return error;
if (S_ISLNK(file->mode))
@@ -177,145 +196,421 @@ static int checkout_blob(
blob, git_buf_cstr(data->path), data->can_symlink);
else
error = blob_content_to_file(
- blob, git_buf_cstr(data->path), file->mode, data->checkout_opts);
+ blob, git_buf_cstr(data->path), file->mode, data->opts);
git_blob_free(blob);
return error;
}
-static int checkout_remove_the_old(
- void *cb_data, const git_diff_delta *delta, float progress)
+static int retrieve_symlink_caps(git_repository *repo, bool *can_symlink)
{
- struct checkout_diff_data *data = cb_data;
- git_checkout_opts *opts = data->checkout_opts;
+ git_config *cfg;
+ int error;
- GIT_UNUSED(progress);
- data->stats->processed++;
+ if (git_repository_config__weakptr(&cfg, repo) < 0)
+ return -1;
- if ((delta->status == GIT_DELTA_UNTRACKED &&
- (opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) ||
- (delta->status == GIT_DELTA_TYPECHANGE &&
- (opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0))
- {
- data->error = git_futils_rmdir_r(
- delta->new_file.path,
- git_repository_workdir(data->owner),
- GIT_DIRREMOVAL_FILES_AND_DIRS);
+ error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks");
+
+ /* If "core.symlinks" is not found anywhere, default to true. */
+ if (error == GIT_ENOTFOUND) {
+ *can_symlink = true;
+ error = 0;
}
- return data->error;
+ return error;
}
-static int checkout_create_the_new(
- void *cb_data, const git_diff_delta *delta, float progress)
+static void normalize_options(
+ git_checkout_opts *normalized, git_checkout_opts *proposed)
{
- int error = 0;
- struct checkout_diff_data *data = cb_data;
- git_checkout_opts *opts = data->checkout_opts;
- bool do_checkout = false, do_notify = false;
+ assert(normalized);
- GIT_UNUSED(progress);
- data->stats->processed++;
+ if (!proposed)
+ memset(normalized, 0, sizeof(git_checkout_opts));
+ else
+ memmove(normalized, proposed, sizeof(git_checkout_opts));
- if (delta->status == GIT_DELTA_MODIFIED ||
- delta->status == GIT_DELTA_TYPECHANGE)
- {
- if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0)
- do_checkout = true;
- else if (opts->skipped_notify_cb != NULL)
- do_notify = !data->create_submodules;
+ /* implied checkout strategies */
+ if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_MODIFIED) != 0 ||
+ (normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_UNMODIFIED;
+
+ if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_MISSING;
+
+ /* opts->disable_filters is false by default */
+
+ if (!normalized->dir_mode)
+ normalized->dir_mode = GIT_DIR_MODE;
+
+ if (!normalized->file_open_flags)
+ normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
+}
+
+enum {
+ CHECKOUT_ACTION__NONE = 0,
+ CHECKOUT_ACTION__REMOVE = 1,
+ CHECKOUT_ACTION__UPDATE_BLOB = 2,
+ CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
+ CHECKOUT_ACTION__CONFLICT = 8,
+ CHECKOUT_ACTION__MAX = 8
+};
+
+static int checkout_confirm_update_blob(
+ checkout_diff_data *data,
+ const git_diff_delta *delta,
+ int action)
+{
+ int error;
+ unsigned int strat = data->opts->checkout_strategy;
+ struct stat st;
+ bool update_only = ((strat & GIT_CHECKOUT_UPDATE_ONLY) != 0);
+
+ /* for typechange, remove the old item first */
+ if (delta->status == GIT_DELTA_TYPECHANGE) {
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else
+ action |= CHECKOUT_ACTION__REMOVE;
+
+ return action;
}
- else if (delta->status == GIT_DELTA_DELETED &&
- (opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0)
- do_checkout = true;
- if (do_notify) {
- if (opts->skipped_notify_cb(
- delta->old_file.path, &delta->old_file.oid,
- delta->old_file.mode, opts->notify_payload))
- {
- giterr_clear();
- error = GIT_EUSER;
+ git_buf_truncate(data->path, data->workdir_len);
+ if (git_buf_puts(data->path, delta->new_file.path) < 0)
+ return -1;
+
+ if ((error = p_lstat_posixly(git_buf_cstr(data->path), &st)) < 0) {
+ if (errno == ENOENT) {
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ } else if (errno == ENOTDIR) {
+ /* File exists where a parent dir needs to go - i.e. untracked
+ * typechange. Ignore if UPDATE_ONLY, remove if allowed.
+ */
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ action |= CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
}
+ /* otherwise let error happen when we attempt blob checkout later */
+ }
+ else if (S_ISDIR(st.st_mode)) {
+ /* Directory exists where a blob needs to go - i.e. untracked
+ * typechange. Ignore if UPDATE_ONLY, remove if allowed.
+ */
+ if (update_only)
+ action = CHECKOUT_ACTION__NONE;
+ else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
+ action |= CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
}
- if (do_checkout) {
- bool is_submodule = S_ISGITLINK(delta->old_file.mode);
+ return action;
+}
- if (is_submodule)
- data->found_submodules = true;
+static int checkout_action_for_delta(
+ checkout_diff_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *head_entry)
+{
+ int action = CHECKOUT_ACTION__NONE;
+ unsigned int strat = data->opts->checkout_strategy;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ if (!head_entry) {
+ /* file independently created in wd, even though not in HEAD */
+ if ((strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ else if (!git_oid_equal(&head_entry->oid, &delta->old_file.oid)) {
+ /* working directory was independently updated to match index */
+ if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ break;
+
+ case GIT_DELTA_ADDED:
+ /* Impossible. New files should be UNTRACKED or TYPECHANGE */
+ action = CHECKOUT_ACTION__CONFLICT;
+ break;
+
+ case GIT_DELTA_DELETED:
+ if (head_entry && /* working dir missing, but exists in HEAD */
+ (strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ break;
+
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_TYPECHANGE:
+ if (!head_entry) {
+ /* working dir was independently updated & does not match index */
+ if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ }
+ else if (git_oid_equal(&head_entry->oid, &delta->new_file.oid))
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
+ action = CHECKOUT_ACTION__CONFLICT;
+ else
+ action = CHECKOUT_ACTION__UPDATE_BLOB;
+ break;
+
+ case GIT_DELTA_UNTRACKED:
+ if (!head_entry) {
+ if ((strat & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0)
+ action = CHECKOUT_ACTION__REMOVE;
+ }
+ else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) != 0) {
+ action = CHECKOUT_ACTION__REMOVE;
+ } else if ((strat & GIT_CHECKOUT_UPDATE_UNMODIFIED) != 0) {
+ git_oid wd_oid;
+
+ /* if HEAD matches workdir, then remove, else conflict */
+
+ if (git_oid_iszero(&delta->new_file.oid) &&
+ git_diff__oid_for_file(
+ data->repo, delta->new_file.path, delta->new_file.mode,
+ delta->new_file.size, &wd_oid) < 0)
+ action = -1;
+ else if (git_oid_equal(&head_entry->oid, &wd_oid))
+ action = CHECKOUT_ACTION__REMOVE;
+ else
+ action = CHECKOUT_ACTION__CONFLICT;
+ } else {
+ /* present in HEAD and workdir, but absent in index */
+ action = CHECKOUT_ACTION__CONFLICT;
+ }
+ break;
- if (!is_submodule && !data->create_submodules)
- error = checkout_blob(data, &delta->old_file);
+ case GIT_DELTA_IGNORED:
+ default:
+ /* just skip these files */
+ break;
+ }
+
+ if (action > 0 && (action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
+ if (S_ISGITLINK(delta->old_file.mode))
+ action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
+ CHECKOUT_ACTION__UPDATE_SUBMODULE;
- else if (is_submodule && data->create_submodules)
- error = checkout_submodule(data, &delta->old_file);
+ action = checkout_confirm_update_blob(data, delta, action);
+ }
+
+ if (action == CHECKOUT_ACTION__CONFLICT &&
+ data->opts->conflict_cb != NULL &&
+ data->opts->conflict_cb(
+ delta->old_file.path, &delta->old_file.oid,
+ delta->old_file.mode, delta->new_file.mode,
+ data->opts->conflict_payload) != 0)
+ {
+ giterr_clear();
+ action = GIT_EUSER;
}
- if (error)
- data->error = error;
+ if (action > 0 && (strat & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ action = (action & ~CHECKOUT_ACTION__REMOVE);
- return error;
+ return action;
}
-static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink)
+static int checkout_get_actions(
+ uint32_t **actions_ptr,
+ size_t **counts_ptr,
+ checkout_diff_data *data)
{
- git_config *cfg;
int error;
+ git_diff_list *diff = data->diff;
+ git_diff_delta *delta;
+ size_t i, *counts = NULL;
+ uint32_t *actions = NULL;
+ git_tree *head = NULL;
+ git_iterator *hiter = NULL;
+ char *pfx = git_pathspec_prefix(&data->opts->paths);
+ const git_index_entry *he;
+
+ /* if there is no HEAD, that's okay - we'll make an empty iterator */
+ (void)git_repository_head_tree(&head, data->repo);
+
+ if ((error = git_iterator_for_tree_range(
+ &hiter, data->repo, head, pfx, pfx)) < 0)
+ goto fail;
+
+ if ((diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
+ !hiter->ignore_case &&
+ (error = git_iterator_spoolandsort(
+ &hiter, hiter, diff->entrycomp, true)) < 0)
+ goto fail;
+
+ if ((error = git_iterator_current(hiter, &he)) < 0)
+ goto fail;
+
+ git__free(pfx);
+ pfx = NULL;
+
+ *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));
+ *actions_ptr = actions = git__calloc(diff->deltas.length, sizeof(uint32_t));
+ if (!counts || !actions) {
+ error = -1;
+ goto fail;
+ }
- if (git_repository_config__weakptr(&cfg, repo) < 0)
- return -1;
+ git_vector_foreach(&diff->deltas, i, delta) {
+ int cmp = -1, act;
+
+ /* try to track HEAD entries parallel to deltas */
+ while (he) {
+ cmp = S_ISDIR(delta->new_file.mode) ?
+ diff->pfxcomp(he->path, delta->new_file.path) :
+ diff->strcomp(he->path, delta->old_file.path);
+ if (cmp >= 0)
+ break;
+ if (git_iterator_advance(hiter, &he) < 0)
+ he = NULL;
+ }
- error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks");
+ act = checkout_action_for_delta(data, delta, !cmp ? he : NULL);
- /*
- * When no "core.symlinks" entry is found in any of the configuration
- * store (local, global or system), default value is "true".
- */
- if (error == GIT_ENOTFOUND) {
- *can_symlink = true;
- error = 0;
+ if (act < 0) {
+ error = act;
+ goto fail;
+ }
+
+ if (!cmp && git_iterator_advance(hiter, &he) < 0)
+ he = NULL;
+
+ actions[i] = act;
+
+ if (act & CHECKOUT_ACTION__REMOVE)
+ counts[CHECKOUT_ACTION__REMOVE]++;
+ if (act & CHECKOUT_ACTION__UPDATE_BLOB)
+ counts[CHECKOUT_ACTION__UPDATE_BLOB]++;
+ if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++;
+ if (act & CHECKOUT_ACTION__CONFLICT)
+ counts[CHECKOUT_ACTION__CONFLICT]++;
}
- return error;
+ if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
+ (data->opts->checkout_strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
+ {
+ giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",
+ (int)counts[CHECKOUT_ACTION__CONFLICT]);
+ goto fail;
+ }
+
+ git_iterator_free(hiter);
+ git_tree_free(head);
+
+ return 0;
+
+fail:
+ *counts_ptr = NULL;
+ git__free(counts);
+ *actions_ptr = NULL;
+ git__free(actions);
+
+ git_iterator_free(hiter);
+ git_tree_free(head);
+ git__free(pfx);
+
+ return -1;
}
-static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *proposed)
+static int checkout_remove_the_old(
+ git_diff_list *diff,
+ unsigned int *actions,
+ checkout_diff_data *data)
{
- assert(normalized);
+ git_diff_delta *delta;
+ size_t i;
- if (!proposed)
- memset(normalized, 0, sizeof(git_checkout_opts));
- else
- memmove(normalized, proposed, sizeof(git_checkout_opts));
+ git_buf_truncate(data->path, data->workdir_len);
- /* Default options */
- if (!normalized->checkout_strategy)
- normalized->checkout_strategy = GIT_CHECKOUT_DEFAULT;
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__REMOVE) {
+ int error = git_futils_rmdir_r(
+ delta->new_file.path,
+ git_buf_cstr(data->path), /* here set to work dir root */
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS |
+ GIT_RMDIR_REMOVE_BLOCKERS);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->new_file.path);
+ }
+ }
- /* opts->disable_filters is false by default */
- if (!normalized->dir_mode)
- normalized->dir_mode = GIT_DIR_MODE;
+ return 0;
+}
- if (!normalized->file_open_flags)
- normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
+static int checkout_create_the_new(
+ git_diff_list *diff,
+ unsigned int *actions,
+ checkout_diff_data *data)
+{
+ git_diff_delta *delta;
+ size_t i;
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
+ int error = checkout_blob(data, &delta->old_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->old_file.path);
+ }
+ }
+
+ return 0;
+}
+
+static int checkout_create_submodules(
+ git_diff_list *diff,
+ unsigned int *actions,
+ checkout_diff_data *data)
+{
+ git_diff_delta *delta;
+ size_t i;
+
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
+ int error = checkout_submodule(data, &delta->old_file);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, delta->old_file.path);
+ }
+ }
+
+ return 0;
}
int git_checkout_index(
git_repository *repo,
- git_checkout_opts *opts,
- git_indexer_stats *stats)
+ git_index *index,
+ git_checkout_opts *opts)
{
git_diff_list *diff = NULL;
- git_indexer_stats dummy_stats;
-
git_diff_options diff_opts = {0};
git_checkout_opts checkout_opts;
-
- struct checkout_diff_data data;
+ checkout_diff_data data;
git_buf workdir = GIT_BUF_INIT;
-
+ uint32_t *actions = NULL;
+ size_t *counts = NULL;
int error;
assert(repo);
@@ -324,14 +619,13 @@ int git_checkout_index(
return error;
diff_opts.flags =
- GIT_DIFF_INCLUDE_UNTRACKED |
- GIT_DIFF_INCLUDE_TYPECHANGE |
- GIT_DIFF_SKIP_BINARY_CHECK;
+ GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_SKIP_BINARY_CHECK;
if (opts && opts->paths.count > 0)
diff_opts.pathspec = opts->paths;
- if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0)
+ if ((error = git_diff_workdir_to_index(&diff, repo, index, &diff_opts)) < 0)
goto cleanup;
if ((error = git_buf_puts(&workdir, git_repository_workdir(repo))) < 0)
@@ -339,27 +633,10 @@ int git_checkout_index(
normalize_options(&checkout_opts, opts);
- if (!stats)
- stats = &dummy_stats;
-
- stats->processed = 0;
- /* total based on 3 passes, but it might be 2 if no submodules */
- stats->total = (unsigned int)git_diff_num_deltas(diff) * 3;
-
- memset(&data, 0, sizeof(data));
-
- data.path = &workdir;
- data.workdir_len = git_buf_len(&workdir);
- data.checkout_opts = &checkout_opts;
- data.stats = stats;
- data.owner = repo;
-
- if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0)
- goto cleanup;
-
- /* Checkout is best performed with three passes through the diff.
+ /* Checkout is best performed with up to four passes through the diff.
*
- * 1. First do removes, because we iterate in alphabetical order, thus
+ * 0. Figure out what actions should be taken and record for later.
+ * 1. Next do removes, because we iterate in alphabetical order, thus
* a new untracked directory will end up sorted *after* a blob that
* should be checked out with the same name.
* 2. Then checkout all blobs.
@@ -367,23 +644,45 @@ int git_checkout_index(
* checked out during pass #2.
*/
- if (!(error = git_diff_foreach(
- diff, &data, checkout_remove_the_old, NULL, NULL)) &&
- !(error = git_diff_foreach(
- diff, &data, checkout_create_the_new, NULL, NULL)) &&
- data.found_submodules)
- {
- data.create_submodules = true;
- error = git_diff_foreach(
- diff, &data, checkout_create_the_new, NULL, NULL);
- }
+ memset(&data, 0, sizeof(data));
+ data.path = &workdir;
+ data.workdir_len = git_buf_len(&workdir);
+ data.repo = repo;
+ data.diff = diff;
+ data.opts = &checkout_opts;
+
+ if ((error = checkout_get_actions(&actions, &counts, &data)) < 0)
+ goto cleanup;
+
+ data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
+ counts[CHECKOUT_ACTION__UPDATE_BLOB] +
+ counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];
+
+ if ((error = retrieve_symlink_caps(repo, &data.can_symlink)) < 0)
+ goto cleanup;
+
+ report_progress(&data, NULL); /* establish 0 baseline */
+
+ if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
+ (error = checkout_remove_the_old(diff, actions, &data)) < 0)
+ goto cleanup;
- stats->processed = stats->total;
+ if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&
+ (error = checkout_create_the_new(diff, actions, &data)) < 0)
+ goto cleanup;
+
+ if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&
+ (error = checkout_create_submodules(diff, actions, &data)) < 0)
+ goto cleanup;
+
+ assert(data.completed_steps == data.total_steps);
cleanup:
if (error == GIT_EUSER)
- error = (data.error != 0) ? data.error : -1;
+ giterr_clear();
+ git__free(actions);
+ git__free(counts);
git_diff_list_free(diff);
git_buf_free(&workdir);
@@ -393,58 +692,49 @@ cleanup:
int git_checkout_tree(
git_repository *repo,
git_object *treeish,
- git_checkout_opts *opts,
- git_indexer_stats *stats)
+ git_checkout_opts *opts)
{
+ int error = 0;
git_index *index = NULL;
git_tree *tree = NULL;
- int error;
-
assert(repo && treeish);
if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
- giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree.");
- return GIT_ERROR;
+ giterr_set(
+ GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");
+ return -1;
}
- if ((error = git_repository_index(&index, repo)) < 0)
- goto cleanup;
+ /* TODO: create a temp index, load tree there and check it out */
- if ((error = git_index_read_tree(index, tree, NULL)) < 0)
- goto cleanup;
+ /* load paths in tree that match pathspec into index */
+ if (!(error = git_repository_index(&index, repo)) &&
+ !(error = git_index_read_tree_match(
+ index, tree, opts ? &opts->paths : NULL)) &&
+ !(error = git_index_write(index)))
+ error = git_checkout_index(repo, NULL, opts);
- if ((error = git_index_write(index)) < 0)
- goto cleanup;
-
- error = git_checkout_index(repo, opts, stats);
-
-cleanup:
git_index_free(index);
git_tree_free(tree);
+
return error;
}
int git_checkout_head(
git_repository *repo,
- git_checkout_opts *opts,
- git_indexer_stats *stats)
+ git_checkout_opts *opts)
{
- git_reference *head;
int error;
+ git_reference *head = NULL;
git_object *tree = NULL;
assert(repo);
- if ((error = git_repository_head(&head, repo)) < 0)
- return error;
-
- if ((error = git_reference_peel(&tree, head, GIT_OBJ_TREE)) < 0)
- goto cleanup;
+ if (!(error = git_repository_head(&head, repo)) &&
+ !(error = git_reference_peel(&tree, head, GIT_OBJ_TREE)))
+ error = git_checkout_tree(repo, tree, opts);
- error = git_checkout_tree(repo, tree, opts, stats);
-
-cleanup:
git_reference_free(head);
git_object_free(tree);
diff --git a/src/clone.c b/src/clone.c
index 85e69ad97..9ef6f8100 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -18,7 +18,6 @@
#include "common.h"
#include "remote.h"
-#include "pkt.h"
#include "fileops.h"
#include "refs.h"
#include "path.h"
@@ -171,11 +170,19 @@ static int update_head_to_new_branch(
return error;
}
+static int get_head_callback(git_remote_head *head, void *payload)
+{
+ git_remote_head **destination = (git_remote_head **)payload;
+
+ /* Save the first entry, and terminate the enumeration */
+ *destination = head;
+ return 1;
+}
+
static int update_head_to_remote(git_repository *repo, git_remote *remote)
{
int retcode = -1;
git_remote_head *remote_head;
- git_pkt_ref *pkt;
struct head_info head_info;
git_buf remote_master_name = GIT_BUF_INIT;
@@ -189,8 +196,13 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote)
}
/* Get the remote's HEAD. This is always the first ref in remote->refs. */
- pkt = remote->transport->refs.contents[0];
- remote_head = &pkt->head;
+ remote_head = NULL;
+
+ if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head))
+ return -1;
+
+ assert(remote_head);
+
git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid);
git_buf_init(&head_info.branchname, 16);
head_info.repo = repo;
@@ -248,22 +260,26 @@ cleanup:
-static int setup_remotes_and_fetch(git_repository *repo,
- const char *origin_url,
- git_indexer_stats *fetch_stats)
+static int setup_remotes_and_fetch(
+ git_repository *repo,
+ const char *origin_url,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
{
int retcode = GIT_ERROR;
git_remote *origin = NULL;
- git_off_t bytes = 0;
- git_indexer_stats dummy_stats;
-
- if (!fetch_stats) fetch_stats = &dummy_stats;
/* Create the "origin" remote */
if (!git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, origin_url)) {
+ /*
+ * Don't write FETCH_HEAD, we'll check out the remote tracking
+ * branch ourselves based on the server's default.
+ */
+ git_remote_set_update_fetchhead(origin, 0);
+
/* Connect and download everything */
if (!git_remote_connect(origin, GIT_DIR_FETCH)) {
- if (!git_remote_download(origin, &bytes, fetch_stats)) {
+ if (!git_remote_download(origin, progress_cb, progress_payload)) {
/* Create "origin/foo" branches for all remote branches */
if (!git_remote_update_tips(origin)) {
/* Point HEAD to the same ref as the remote's head */
@@ -311,26 +327,24 @@ static int clone_internal(
git_repository **out,
const char *origin_url,
const char *path,
- git_indexer_stats *fetch_stats,
- git_indexer_stats *checkout_stats,
+ git_transfer_progress_callback fetch_progress_cb,
+ void *fetch_progress_payload,
git_checkout_opts *checkout_opts,
bool is_bare)
{
int retcode = GIT_ERROR;
git_repository *repo = NULL;
- git_indexer_stats dummy_stats;
-
- if (!fetch_stats) fetch_stats = &dummy_stats;
if (!path_is_okay(path)) {
return GIT_ERROR;
}
if (!(retcode = git_repository_init(&repo, path, is_bare))) {
- if ((retcode = setup_remotes_and_fetch(repo, origin_url, fetch_stats)) < 0) {
+ if ((retcode = setup_remotes_and_fetch(repo, origin_url,
+ fetch_progress_cb, fetch_progress_payload)) < 0) {
/* Failed to fetch; clean up */
git_repository_free(repo);
- git_futils_rmdir_r(path, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
+ git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES);
} else {
*out = repo;
retcode = 0;
@@ -338,15 +352,17 @@ static int clone_internal(
}
if (!retcode && should_checkout(repo, is_bare, checkout_opts))
- retcode = git_checkout_head(*out, checkout_opts, checkout_stats);
+ retcode = git_checkout_head(*out, checkout_opts);
return retcode;
}
-int git_clone_bare(git_repository **out,
- const char *origin_url,
- const char *dest_path,
- git_indexer_stats *fetch_stats)
+int git_clone_bare(
+ git_repository **out,
+ const char *origin_url,
+ const char *dest_path,
+ git_transfer_progress_callback fetch_progress_cb,
+ void *fetch_progress_payload)
{
assert(out && origin_url && dest_path);
@@ -354,19 +370,20 @@ int git_clone_bare(git_repository **out,
out,
origin_url,
dest_path,
- fetch_stats,
- NULL,
+ fetch_progress_cb,
+ fetch_progress_payload,
NULL,
1);
}
-int git_clone(git_repository **out,
- const char *origin_url,
- const char *workdir_path,
- git_indexer_stats *fetch_stats,
- git_indexer_stats *checkout_stats,
- git_checkout_opts *checkout_opts)
+int git_clone(
+ git_repository **out,
+ const char *origin_url,
+ const char *workdir_path,
+ git_transfer_progress_callback fetch_progress_cb,
+ void *fetch_progress_payload,
+ git_checkout_opts *checkout_opts)
{
assert(out && origin_url && workdir_path);
@@ -374,8 +391,8 @@ int git_clone(git_repository **out,
out,
origin_url,
workdir_path,
- fetch_stats,
- checkout_stats,
+ fetch_progress_cb,
+ fetch_progress_payload,
checkout_opts,
0);
}
diff --git a/src/common.h b/src/common.h
index 747bbf7ce..a35239e3d 100644
--- a/src/common.h
+++ b/src/common.h
@@ -69,7 +69,4 @@ void giterr_set_regex(const regex_t *regex, int error_code);
#include "util.h"
-typedef struct git_transport git_transport;
-typedef struct gitno_buffer gitno_buffer;
-
#endif /* INCLUDE_common_h__ */
diff --git a/src/config.c b/src/config.c
index f9bd205af..fc9ea65fe 100644
--- a/src/config.c
+++ b/src/config.c
@@ -90,6 +90,13 @@ int git_config_add_file_ondisk(
git_config_file *file = NULL;
int res;
+ assert(cfg && path);
+
+ if (!git_path_isfile(path)) {
+ giterr_set(GITERR_CONFIG, "Cannot find config file '%s'", path);
+ return GIT_ENOTFOUND;
+ }
+
if (git_config_file__ondisk(&file, path) < 0)
return -1;
@@ -105,17 +112,22 @@ int git_config_add_file_ondisk(
return 0;
}
-int git_config_open_ondisk(git_config **cfg, const char *path)
+int git_config_open_ondisk(git_config **out, const char *path)
{
- if (git_config_new(cfg) < 0)
- return -1;
+ int error;
+ git_config *config;
+
+ *out = NULL;
- if (git_config_add_file_ondisk(*cfg, path, GIT_CONFIG_LEVEL_LOCAL, 0) < 0) {
- git_config_free(*cfg);
+ if (git_config_new(&config) < 0)
return -1;
- }
- return 0;
+ if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0)
+ git_config_free(config);
+ else
+ *out = config;
+
+ return error;
}
static int find_internal_file_by_level(
@@ -127,8 +139,6 @@ static int find_internal_file_by_level(
file_internal *internal;
unsigned int i;
- assert(cfg->files.length);
-
/* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file
* which has the highest level. As config files are stored in a vector
* sorted by decreasing order of level, getting the file at position 0
@@ -267,6 +277,20 @@ int git_config_add_file(
return 0;
}
+int git_config_refresh(git_config *cfg)
+{
+ int error = 0;
+ unsigned int i;
+
+ for (i = 0; i < cfg->files.length && !error; ++i) {
+ file_internal *internal = git_vector_get(&cfg->files, i);
+ git_config_file *file = internal->file;
+ error = file->refresh(file);
+ }
+
+ return error;
+}
+
/*
* Loop over all the variables
*/
@@ -381,32 +405,19 @@ int git_config_get_int32(int32_t *out, git_config *cfg, const char *name)
return git_config_parse_int32(out, value);
}
-int git_config_get_bool(int *out, git_config *cfg, const char *name)
-{
- const char *value;
- int ret;
-
- if ((ret = git_config_get_string(&value, cfg, name)) < 0)
- return ret;
-
- return git_config_parse_bool(out, value);
-}
-
static int get_string_at_file(const char **out, git_config_file *file, const char *name)
{
const git_config_entry *entry;
int res;
- *out = NULL;
-
res = file->get(file, name, &entry);
- if (res != GIT_ENOTFOUND)
+ if (!res)
*out = entry->value;
return res;
}
-int git_config_get_string(const char **out, git_config *cfg, const char *name)
+static int get_string(const char **out, git_config *cfg, const char *name)
{
file_internal *internal;
unsigned int i;
@@ -423,7 +434,30 @@ int git_config_get_string(const char **out, git_config *cfg, const char *name)
return GIT_ENOTFOUND;
}
-int git_config_get_config_entry(const git_config_entry **out, git_config *cfg, const char *name)
+int git_config_get_bool(int *out, git_config *cfg, const char *name)
+{
+ const char *value = NULL;
+ int ret;
+
+ if ((ret = get_string(&value, cfg, name)) < 0)
+ return ret;
+
+ return git_config_parse_bool(out, value);
+}
+
+int git_config_get_string(const char **out, git_config *cfg, const char *name)
+{
+ int ret;
+ const char *str = NULL;
+
+ if ((ret = get_string(&str, cfg, name)) < 0)
+ return ret;
+
+ *out = str == NULL ? "" : str;
+ return 0;
+}
+
+int git_config_get_entry(const git_config_entry **out, git_config *cfg, const char *name)
{
file_internal *internal;
unsigned int i;
@@ -720,3 +754,81 @@ fail_parse:
giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);
return -1;
}
+
+struct rename_data
+{
+ git_config *config;
+ const char *old_name;
+ const char *new_name;
+};
+
+static int rename_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
+{
+ struct rename_data *data = (struct rename_data *)payload;
+
+ if (data->new_name != NULL) {
+ git_buf name = GIT_BUF_INIT;
+ int error;
+
+ if (git_buf_printf(
+ &name,
+ "%s.%s",
+ data->new_name,
+ entry->name + strlen(data->old_name) + 1) < 0)
+ return -1;
+
+ error = git_config_set_string(
+ data->config,
+ git_buf_cstr(&name),
+ entry->value);
+
+ git_buf_free(&name);
+
+ if (error)
+ return error;
+ }
+
+ return git_config_delete(data->config, entry->name);
+}
+
+int git_config_rename_section(
+ git_repository *repo,
+ const char *old_section_name,
+ const char *new_section_name)
+{
+ git_config *config;
+ git_buf pattern = GIT_BUF_INIT;
+ int error = -1;
+ struct rename_data data;
+
+ git_buf_puts_escape_regex(&pattern, old_section_name);
+ git_buf_puts(&pattern, "\\..+");
+ if (git_buf_oom(&pattern))
+ goto cleanup;
+
+ if (git_repository_config__weakptr(&config, repo) < 0)
+ goto cleanup;
+
+ data.config = config;
+ data.old_name = old_section_name;
+ data.new_name = new_section_name;
+
+ if ((error = git_config_foreach_match(
+ config,
+ git_buf_cstr(&pattern),
+ rename_config_entries_cb, &data)) < 0) {
+ giterr_set(GITERR_CONFIG,
+ "Cannot rename config section '%s' to '%s'",
+ old_section_name,
+ new_section_name);
+ goto cleanup;
+ }
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&pattern);
+ return error;
+}
diff --git a/src/config.h b/src/config.h
index 16b8413b7..c7595ee79 100644
--- a/src/config.h
+++ b/src/config.h
@@ -27,4 +27,22 @@ extern int git_config_find_global_r(git_buf *global_config_path);
extern int git_config_find_xdg_r(git_buf *system_config_path);
extern int git_config_find_system_r(git_buf *system_config_path);
+extern int git_config_rename_section(
+ git_repository *repo,
+ const char *old_section_name, /* eg "branch.dummy" */
+ const char *new_section_name); /* NULL to drop the old section */
+
+/**
+ * Create a configuration file backend for ondisk files
+ *
+ * These are the normal `.gitconfig` files that Core Git
+ * processes. Note that you first have to add this file to a
+ * configuration object before you can query it for configuration
+ * variables.
+ *
+ * @param out the new backend
+ * @param path where the config file is located
+ */
+extern int git_config_file__ondisk(struct git_config_file **out, const char *path);
+
#endif
diff --git a/src/config_file.c b/src/config_file.c
index 92fe13296..4d9f99986 100644
--- a/src/config_file.c
+++ b/src/config_file.c
@@ -75,7 +75,11 @@ typedef struct {
int eof;
} reader;
- char *file_path;
+ char *file_path;
+ time_t file_mtime;
+ size_t file_size;
+
+ unsigned int level;
} diskfile_backend;
static int config_parse(diskfile_backend *cfg_file, unsigned int level);
@@ -150,25 +154,53 @@ static int config_open(git_config_file *cfg, unsigned int level)
int res;
diskfile_backend *b = (diskfile_backend *)cfg;
+ b->level = level;
+
b->values = git_strmap_alloc();
GITERR_CHECK_ALLOC(b->values);
git_buf_init(&b->reader.buffer, 0);
- res = git_futils_readbuffer(&b->reader.buffer, b->file_path);
+ res = git_futils_readbuffer_updated(
+ &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL);
/* It's fine if the file doesn't exist */
if (res == GIT_ENOTFOUND)
return 0;
- if (res < 0 || config_parse(b, level) < 0) {
+ if (res < 0 || (res = config_parse(b, level)) < 0) {
free_vars(b->values);
b->values = NULL;
- git_buf_free(&b->reader.buffer);
- return -1;
}
git_buf_free(&b->reader.buffer);
- return 0;
+ return res;
+}
+
+static int config_refresh(git_config_file *cfg)
+{
+ int res, updated = 0;
+ diskfile_backend *b = (diskfile_backend *)cfg;
+ git_strmap *old_values;
+
+ res = git_futils_readbuffer_updated(
+ &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated);
+ if (res < 0 || !updated)
+ return (res == GIT_ENOTFOUND) ? 0 : res;
+
+ /* need to reload - store old values and prep for reload */
+ old_values = b->values;
+ b->values = git_strmap_alloc();
+ GITERR_CHECK_ALLOC(b->values);
+
+ if ((res = config_parse(b, b->level)) < 0) {
+ free_vars(b->values);
+ b->values = old_values;
+ } else {
+ free_vars(old_values);
+ }
+
+ git_buf_free(&b->reader.buffer);
+ return res;
}
static void backend_free(git_config_file *_backend)
@@ -527,6 +559,7 @@ int git_config_file__ondisk(git_config_file **out, const char *path)
backend->parent.set_multivar = config_set_multivar;
backend->parent.del = config_delete;
backend->parent.foreach = file_foreach;
+ backend->parent.refresh = config_refresh;
backend->parent.free = backend_free;
*out = (git_config_file *)backend;
@@ -891,7 +924,7 @@ static int strip_comments(char *line, int in_quotes)
}
/* skip any space at the end */
- if (git__isspace(ptr[-1])) {
+ if (ptr > line && git__isspace(ptr[-1])) {
ptr--;
}
ptr[0] = '\0';
@@ -1197,8 +1230,12 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p
git__free(section);
git__free(current_section);
+ /* refresh stats - if this errors, then commit will error too */
+ (void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file);
+
result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
git_buf_free(&cfg->reader.buffer);
+
return result;
rewrite_fail:
@@ -1361,7 +1398,7 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
value_start = var_end + 1;
do var_end--;
- while (git__isspace(*var_end));
+ while (var_end>line && git__isspace(*var_end));
*var_name = git__strndup(line, var_end - line + 1);
GITERR_CHECK_ALLOC(*var_name);
@@ -1395,8 +1432,10 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val
else if (value_start[0] != '\0') {
*var_value = fixup_line(value_start, 0);
GITERR_CHECK_ALLOC(*var_value);
+ } else { /* equals sign but missing rhs */
+ *var_value = git__strdup("");
+ GITERR_CHECK_ALLOC(*var_value);
}
-
}
git__free(line);
diff --git a/src/diff.c b/src/diff.c
index 9f693bebf..d6f5bd454 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -10,76 +10,7 @@
#include "config.h"
#include "attr_file.h"
#include "filter.h"
-
-static char *diff_prefix_from_pathspec(const git_strarray *pathspec)
-{
- git_buf prefix = GIT_BUF_INIT;
- const char *scan;
-
- if (git_buf_common_prefix(&prefix, pathspec) < 0)
- return NULL;
-
- /* diff prefix will only be leading non-wildcards */
- for (scan = prefix.ptr; *scan; ++scan) {
- if (git__iswildcard(*scan) &&
- (scan == prefix.ptr || (*(scan - 1) != '\\')))
- break;
- }
- git_buf_truncate(&prefix, scan - prefix.ptr);
-
- if (prefix.size <= 0) {
- git_buf_free(&prefix);
- return NULL;
- }
-
- git_buf_unescape(&prefix);
-
- return git_buf_detach(&prefix);
-}
-
-static bool diff_pathspec_is_interesting(const git_strarray *pathspec)
-{
- const char *str;
-
- if (pathspec == NULL || pathspec->count == 0)
- return false;
- if (pathspec->count > 1)
- return true;
-
- str = pathspec->strings[0];
- if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))
- return false;
- return true;
-}
-
-static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path)
-{
- unsigned int i;
- git_attr_fnmatch *match;
-
- if (!diff->pathspec.length)
- return true;
-
- git_vector_foreach(&diff->pathspec, i, match) {
- int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
-
- if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) &&
- result == FNM_NOMATCH)
- result = p_fnmatch(match->pattern, path, 0);
-
- /* if we didn't match, look for exact dirname prefix match */
- if (result == FNM_NOMATCH &&
- (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
- strncmp(path, match->pattern, match->length) == 0 &&
- path[match->length] == '/')
- result = 0;
-
- if (result == 0)
- return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
- }
-
- return false;
-}
+#include "pathspec.h"
static git_diff_delta *diff_delta__alloc(
git_diff_list *diff,
@@ -110,85 +41,6 @@ static git_diff_delta *diff_delta__alloc(
return delta;
}
-static git_diff_delta *diff_delta__dup(
- const git_diff_delta *d, git_pool *pool)
-{
- git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
- if (!delta)
- return NULL;
-
- memcpy(delta, d, sizeof(git_diff_delta));
-
- delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
- if (delta->old_file.path == NULL)
- goto fail;
-
- if (d->new_file.path != d->old_file.path) {
- delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
- if (delta->new_file.path == NULL)
- goto fail;
- } else {
- delta->new_file.path = delta->old_file.path;
- }
-
- return delta;
-
-fail:
- git__free(delta);
- return NULL;
-}
-
-static git_diff_delta *diff_delta__merge_like_cgit(
- const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
-{
- git_diff_delta *dup;
-
- /* Emulate C git for merging two diffs (a la 'git diff <sha>').
- *
- * When C git does a diff between the work dir and a tree, it actually
- * diffs with the index but uses the workdir contents. This emulates
- * those choices so we can emulate the type of diff.
- *
- * We have three file descriptions here, let's call them:
- * f1 = a->old_file
- * f2 = a->new_file AND b->old_file
- * f3 = b->new_file
- */
-
- /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
- if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
- return diff_delta__dup(a, pool);
-
- /* otherwise, base this diff on the 'b' diff */
- if ((dup = diff_delta__dup(b, pool)) == NULL)
- return NULL;
-
- /* If 'a' status is uninteresting, then we're done */
- if (a->status == GIT_DELTA_UNMODIFIED)
- return dup;
-
- assert(a->status != GIT_DELTA_UNMODIFIED);
- assert(b->status != GIT_DELTA_UNMODIFIED);
-
- /* A cgit exception is that the diff of a file that is only in the
- * index (i.e. not in HEAD nor workdir) is given as empty.
- */
- if (dup->status == GIT_DELTA_DELETED) {
- if (a->status == GIT_DELTA_ADDED)
- dup->status = GIT_DELTA_UNMODIFIED;
- /* else don't overwrite DELETE status */
- } else {
- dup->status = a->status;
- }
-
- git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);
- dup->old_file.mode = a->old_file.mode;
- dup->old_file.size = a->old_file.size;
- dup->old_file.flags = a->old_file.flags;
-
- return dup;
-}
-
static int diff_delta__from_one(
git_diff_list *diff,
git_delta_t status,
@@ -204,7 +56,10 @@ static int diff_delta__from_one(
(diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return 0;
- if (!diff_path_matches_pathspec(diff, entry->path))
+ if (!git_pathspec_match_path(
+ &diff->pathspec, entry->path,
+ (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0,
+ (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
@@ -332,13 +187,34 @@ static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
return git_pool_strndup(pool, prefix, len + 1);
}
-static int diff_delta__cmp(const void *a, const void *b)
+int git_diff_delta__cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcmp(da->old_file.path, db->old_file.path);
return val ? val : ((int)da->status - (int)db->status);
}
+bool git_diff_delta__should_skip(
+ const git_diff_options *opts, const git_diff_delta *delta)
+{
+ uint32_t flags = opts ? opts->flags : 0;
+
+ if (delta->status == GIT_DELTA_UNMODIFIED &&
+ (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_IGNORED &&
+ (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_UNTRACKED &&
+ (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+ return true;
+
+ return false;
+}
+
+
static int config_bool(git_config *cfg, const char *name, int defvalue)
{
int val = defvalue;
@@ -353,7 +229,6 @@ static git_diff_list *git_diff_list_alloc(
git_repository *repo, const git_diff_options *opts)
{
git_config *cfg;
- size_t i;
git_diff_list *diff = git__calloc(1, sizeof(git_diff_list));
if (diff == NULL)
return NULL;
@@ -361,7 +236,7 @@ static git_diff_list *git_diff_list_alloc(
GIT_REFCOUNT_INC(diff);
diff->repo = repo;
- if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 ||
+ if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 ||
git_pool_init(&diff->pool, 1, 0) < 0)
goto fail;
@@ -378,11 +253,28 @@ static git_diff_list *git_diff_list_alloc(
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
+ /* TODO: there are certain config settings where even if we were
+ * not given an options structure, we need the diff list to have one
+ * so that we can store the altered default values.
+ *
+ * - diff.ignoreSubmodules
+ * - diff.mnemonicprefix
+ * - diff.noprefix
+ */
+
if (opts == NULL)
return diff;
memcpy(&diff->opts, opts, sizeof(git_diff_options));
- memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec));
+
+ if(opts->flags & GIT_DIFF_IGNORE_FILEMODE)
+ diff->diffcaps = diff->diffcaps & ~GIT_DIFFCAPS_TRUST_MODE_BITS;
+
+ /* pathspec init will do nothing for empty pathspec */
+ if (git_pathspec_init(&diff->pathspec, &opts->pathspec, &diff->pool) < 0)
+ goto fail;
+
+ /* TODO: handle config diff.mnemonicprefix, diff.noprefix */
diff->opts.old_prefix = diff_strdup_prefix(&diff->pool,
opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT);
@@ -402,35 +294,6 @@ static git_diff_list *git_diff_list_alloc(
if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES)
diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
- /* only copy pathspec if it is "interesting" so we can test
- * diff->pathspec.length > 0 to know if it is worth calling
- * fnmatch as we iterate.
- */
- if (!diff_pathspec_is_interesting(&opts->pathspec))
- return diff;
-
- if (git_vector_init(
- &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0)
- goto fail;
-
- for (i = 0; i < opts->pathspec.count; ++i) {
- int ret;
- const char *pattern = opts->pathspec.strings[i];
- git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
- if (!match)
- goto fail;
- match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
- ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern);
- if (ret == GIT_ENOTFOUND) {
- git__free(match);
- continue;
- } else if (ret < 0)
- goto fail;
-
- if (git_vector_insert(&diff->pathspec, match) < 0)
- goto fail;
- }
-
return diff;
fail:
@@ -441,7 +304,6 @@ fail:
static void diff_list_free(git_diff_list *diff)
{
git_diff_delta *delta;
- git_attr_fnmatch *match;
unsigned int i;
git_vector_foreach(&diff->deltas, i, delta) {
@@ -450,12 +312,7 @@ static void diff_list_free(git_diff_list *diff)
}
git_vector_free(&diff->deltas);
- git_vector_foreach(&diff->pathspec, i, match) {
- git__free(match);
- diff->pathspec.contents[i] = NULL;
- }
- git_vector_free(&diff->pathspec);
-
+ git_pathspec_free(&diff->pathspec);
git_pool_clear(&diff->pool);
git__free(diff);
}
@@ -473,24 +330,39 @@ void git_diff_list_addref(git_diff_list *diff)
GIT_REFCOUNT_INC(diff);
}
-static int oid_for_workdir_item(
+int git_diff__oid_for_file(
git_repository *repo,
- const git_index_entry *item,
+ const char *path,
+ uint16_t mode,
+ git_off_t size,
git_oid *oid)
{
int result = 0;
git_buf full_path = GIT_BUF_INIT;
if (git_buf_joinpath(
- &full_path, git_repository_workdir(repo), item->path) < 0)
+ &full_path, git_repository_workdir(repo), path) < 0)
return -1;
+ if (!mode) {
+ struct stat st;
+
+ if (p_stat(path, &st) < 0) {
+ giterr_set(GITERR_OS, "Could not stat '%s'", path);
+ result = -1;
+ goto cleanup;
+ }
+
+ mode = st.st_mode;
+ size = st.st_size;
+ }
+
/* calculate OID for file if possible */
- if (S_ISGITLINK(item->mode)) {
+ if (S_ISGITLINK(mode)) {
git_submodule *sm;
const git_oid *sm_oid;
- if (!git_submodule_lookup(&sm, repo, item->path) &&
+ if (!git_submodule_lookup(&sm, repo, path) &&
(sm_oid = git_submodule_wd_oid(sm)) != NULL)
git_oid_cpy(oid, sm_oid);
else {
@@ -500,23 +372,22 @@ static int oid_for_workdir_item(
giterr_clear();
memset(oid, 0, sizeof(*oid));
}
- } else if (S_ISLNK(item->mode))
+ } else if (S_ISLNK(mode)) {
result = git_odb__hashlink(oid, full_path.ptr);
- else if (!git__is_sizet(item->file_size)) {
- giterr_set(GITERR_OS, "File size overflow for 32-bit systems");
+ } else if (!git__is_sizet(size)) {
+ giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path);
result = -1;
} else {
git_vector filters = GIT_VECTOR_INIT;
- result = git_filters_load(
- &filters, repo, item->path, GIT_FILTER_TO_ODB);
+ result = git_filters_load(&filters, repo, path, GIT_FILTER_TO_ODB);
if (result >= 0) {
int fd = git_futils_open_ro(full_path.ptr);
if (fd < 0)
result = fd;
else {
result = git_odb__hashfd_filtered(
- oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB, &filters);
+ oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters);
p_close(fd);
}
}
@@ -524,8 +395,8 @@ static int oid_for_workdir_item(
git_filters_free(&filters);
}
+cleanup:
git_buf_free(&full_path);
-
return result;
}
@@ -546,7 +417,10 @@ static int maybe_modified(
GIT_UNUSED(old_iter);
- if (!diff_path_matches_pathspec(diff, oitem->path))
+ if (!git_pathspec_match_path(
+ &diff->pathspec, oitem->path,
+ (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0,
+ (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0))
return 0;
/* on platforms with no symlinks, preserve mode of existing symlinks */
@@ -582,8 +456,7 @@ static int maybe_modified(
}
/* if oids and modes match, then file is unmodified */
- else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 &&
- omode == nmode)
+ else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode)
status = GIT_DELTA_UNMODIFIED;
/* if we have an unknown OID and a workdir iterator, then check some
@@ -637,44 +510,28 @@ static int maybe_modified(
* haven't calculated the OID of the new item, then calculate it now
*/
if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) {
- if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
- return -1;
- else if (omode == nmode && git_oid_equal(&oitem->oid, &noid))
+ if (!use_noid) {
+ if (git_diff__oid_for_file(diff->repo,
+ nitem->path, nitem->mode, nitem->file_size, &noid) < 0)
+ return -1;
+ use_noid = &noid;
+ }
+ if (omode == nmode && git_oid_equal(&oitem->oid, use_noid))
status = GIT_DELTA_UNMODIFIED;
-
- /* store calculated oid so we don't have to recalc later */
- use_noid = &noid;
}
return diff_delta__from_two(
diff, status, oitem, omode, nitem, nmode, use_noid);
}
-static int git_index_entry_cmp_case(const void *a, const void *b)
-{
- const git_index_entry *entry_a = a;
- const git_index_entry *entry_b = b;
-
- return strcmp(entry_a->path, entry_b->path);
-}
-
-static int git_index_entry_cmp_icase(const void *a, const void *b)
-{
- const git_index_entry *entry_a = a;
- const git_index_entry *entry_b = b;
-
- return strcasecmp(entry_a->path, entry_b->path);
-}
-
static bool entry_is_prefixed(
+ git_diff_list *diff,
const git_index_entry *item,
- git_iterator *prefix_iterator,
const git_index_entry *prefix_item)
{
size_t pathlen;
- if (!prefix_item ||
- ITERATOR_PREFIXCMP(*prefix_iterator, prefix_item->path, item->path))
+ if (!prefix_item || diff->pfxcomp(prefix_item->path, item->path))
return false;
pathlen = strlen(item->path);
@@ -684,44 +541,67 @@ static bool entry_is_prefixed(
prefix_item->path[pathlen] == '/');
}
-static int diff_from_iterators(
- git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
+static int diff_list_init_from_iterators(
+ git_diff_list *diff,
git_iterator *old_iter,
- git_iterator *new_iter,
- git_diff_list **diff_ptr)
+ git_iterator *new_iter)
{
- const git_index_entry *oitem, *nitem;
- git_buf ignore_prefix = GIT_BUF_INIT;
- git_diff_list *diff = git_diff_list_alloc(repo, opts);
- git_vector_cmp entry_compare;
-
- if (!diff)
- goto fail;
-
diff->old_src = old_iter->type;
diff->new_src = new_iter->type;
/* Use case-insensitive compare if either iterator has
* the ignore_case bit set */
if (!old_iter->ignore_case && !new_iter->ignore_case) {
- entry_compare = git_index_entry_cmp_case;
diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE;
+
+ diff->strcomp = git__strcmp;
+ diff->strncomp = git__strncmp;
+ diff->pfxcomp = git__prefixcmp;
+ diff->entrycomp = git_index_entry__cmp;
} else {
- entry_compare = git_index_entry_cmp_icase;
diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE;
+ diff->strcomp = git__strcasecmp;
+ diff->strncomp = git__strncasecmp;
+ diff->pfxcomp = git__prefixcmp_icase;
+ diff->entrycomp = git_index_entry__cmp_icase;
+ }
+
+ return 0;
+}
+
+static int diff_from_iterators(
+ git_diff_list **diff_ptr,
+ git_repository *repo,
+ git_iterator *old_iter,
+ git_iterator *new_iter,
+ const git_diff_options *opts)
+{
+ int error = 0;
+ const git_index_entry *oitem, *nitem;
+ git_buf ignore_prefix = GIT_BUF_INIT;
+ git_diff_list *diff = git_diff_list_alloc(repo, opts);
+
+ *diff_ptr = NULL;
+
+ if (!diff ||
+ diff_list_init_from_iterators(diff, old_iter, new_iter) < 0)
+ goto fail;
+
+ if (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
/* If one of the iterators doesn't have ignore_case set,
* then that's unfortunate because we'll have to spool
* its data, sort it icase, and then use that for our
* merge join to the other iterator that is icase sorted */
- if (!old_iter->ignore_case) {
- if (git_iterator_spoolandsort(&old_iter, old_iter, git_index_entry_cmp_icase, true) < 0)
- goto fail;
- } else if (!new_iter->ignore_case) {
- if (git_iterator_spoolandsort(&new_iter, new_iter, git_index_entry_cmp_icase, true) < 0)
- goto fail;
- }
+ if (!old_iter->ignore_case &&
+ git_iterator_spoolandsort(
+ &old_iter, old_iter, diff->entrycomp, true) < 0)
+ goto fail;
+
+ if (!new_iter->ignore_case &&
+ git_iterator_spoolandsort(
+ &new_iter, new_iter, diff->entrycomp, true) < 0)
+ goto fail;
}
if (git_iterator_current(old_iter, &oitem) < 0 ||
@@ -732,7 +612,7 @@ static int diff_from_iterators(
while (oitem || nitem) {
/* create DELETED records for old items not matched in new */
- if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) {
+ if (oitem && (!nitem || diff->entrycomp(oitem, nitem) < 0)) {
if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0)
goto fail;
@@ -740,7 +620,7 @@ static int diff_from_iterators(
* instead of just generating a DELETE record
*/
if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
- entry_is_prefixed(oitem, new_iter, nitem))
+ entry_is_prefixed(diff, oitem, nitem))
{
/* this entry has become a tree! convert to TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
@@ -757,13 +637,12 @@ static int diff_from_iterators(
/* create ADDED, TRACKED, or IGNORED records for new items not
* matched in old (and/or descend into directories as needed)
*/
- else if (nitem && (!oitem || entry_compare(oitem, nitem) > 0)) {
+ else if (nitem && (!oitem || diff->entrycomp(oitem, nitem) > 0)) {
git_delta_t delta_type = GIT_DELTA_UNTRACKED;
/* check if contained in ignored parent directory */
if (git_buf_len(&ignore_prefix) &&
- ITERATOR_PREFIXCMP(*old_iter, nitem->path,
- git_buf_cstr(&ignore_prefix)) == 0)
+ diff->pfxcomp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0)
delta_type = GIT_DELTA_IGNORED;
if (S_ISDIR(nitem->mode)) {
@@ -772,7 +651,7 @@ static int diff_from_iterators(
* directories and it is not under an ignored directory.
*/
bool contains_tracked =
- entry_is_prefixed(nitem, old_iter, oitem);
+ entry_is_prefixed(diff, nitem, oitem);
bool recurse_untracked =
(delta_type == GIT_DELTA_UNTRACKED &&
(diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
@@ -836,7 +715,7 @@ static int diff_from_iterators(
*/
if (delta_type != GIT_DELTA_IGNORED &&
(diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
- entry_is_prefixed(nitem, old_iter, oitem))
+ entry_is_prefixed(diff, nitem, oitem))
{
/* this entry was a tree! convert to TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
@@ -854,7 +733,7 @@ static int diff_from_iterators(
* (or ADDED and DELETED pair if type changed)
*/
else {
- assert(oitem && nitem && entry_compare(oitem, nitem) == 0);
+ assert(oitem && nitem && diff->entrycomp(oitem, nitem) == 0);
if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||
git_iterator_advance(old_iter, &oitem) < 0 ||
@@ -863,222 +742,105 @@ static int diff_from_iterators(
}
}
- git_iterator_free(old_iter);
- git_iterator_free(new_iter);
- git_buf_free(&ignore_prefix);
-
*diff_ptr = diff;
- return 0;
fail:
- git_iterator_free(old_iter);
- git_iterator_free(new_iter);
+ if (!*diff_ptr) {
+ git_diff_list_free(diff);
+ error = -1;
+ }
+
git_buf_free(&ignore_prefix);
- git_diff_list_free(diff);
- *diff_ptr = NULL;
- return -1;
+ return error;
}
+#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \
+ git_iterator *a = NULL, *b = NULL; \
+ char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \
+ if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
+ error = diff_from_iterators(diff, repo, a, b, opts); \
+ git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
+ } while (0)
+
int git_diff_tree_to_tree(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts, /**< can be NULL for defaults */
git_tree *old_tree,
git_tree *new_tree,
- git_diff_list **diff)
+ const git_diff_options *opts)
{
- git_iterator *a = NULL, *b = NULL;
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
-
- assert(repo && old_tree && new_tree && diff);
+ int error = 0;
- if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
- git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0)
- return -1;
+ assert(diff && repo);
- git__free(prefix);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx),
+ git_iterator_for_tree_range(&b, repo, new_tree, pfx, pfx)
+ );
- return diff_from_iterators(repo, opts, a, b, diff);
+ return error;
}
int git_diff_index_to_tree(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts,
git_tree *old_tree,
- git_diff_list **diff)
+ git_index *index,
+ const git_diff_options *opts)
{
- git_iterator *a = NULL, *b = NULL;
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
-
- assert(repo && diff);
+ int error = 0;
- if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 ||
- git_iterator_for_index_range(&b, repo, prefix, prefix) < 0)
- goto on_error;
+ assert(diff && repo);
- git__free(prefix);
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
- return diff_from_iterators(repo, opts, a, b, diff);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx),
+ git_iterator_for_index_range(&b, index, pfx, pfx)
+ );
-on_error:
- git__free(prefix);
- git_iterator_free(a);
- return -1;
+ return error;
}
int git_diff_workdir_to_index(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts,
- git_diff_list **diff)
+ git_index *index,
+ const git_diff_options *opts)
{
- git_iterator *a = NULL, *b = NULL;
- int error;
-
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
-
- assert(repo && diff);
+ int error = 0;
- if ((error = git_iterator_for_index_range(&a, repo, prefix, prefix)) < 0 ||
- (error = git_iterator_for_workdir_range(&b, repo, prefix, prefix)) < 0)
- goto on_error;
+ assert(diff && repo);
- git__free(prefix);
+ if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
+ return error;
- return diff_from_iterators(repo, opts, a, b, diff);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_index_range(&a, index, pfx, pfx),
+ git_iterator_for_workdir_range(&b, repo, pfx, pfx)
+ );
-on_error:
- git__free(prefix);
- git_iterator_free(a);
return error;
}
int git_diff_workdir_to_tree(
+ git_diff_list **diff,
git_repository *repo,
- const git_diff_options *opts,
git_tree *old_tree,
- git_diff_list **diff)
-{
- git_iterator *a = NULL, *b = NULL;
- int error;
-
- char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL;
-
- assert(repo && old_tree && diff);
-
- if ((error = git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix)) < 0 ||
- (error = git_iterator_for_workdir_range(&b, repo, prefix, prefix)) < 0)
- goto on_error;
-
- git__free(prefix);
-
- return diff_from_iterators(repo, opts, a, b, diff);
-
-on_error:
- git__free(prefix);
- git_iterator_free(a);
- return error;
-}
-
-
-bool git_diff_delta__should_skip(
- const git_diff_options *opts, const git_diff_delta *delta)
-{
- uint32_t flags = opts ? opts->flags : 0;
-
- if (delta->status == GIT_DELTA_UNMODIFIED &&
- (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
- return true;
-
- if (delta->status == GIT_DELTA_IGNORED &&
- (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
- return true;
-
- if (delta->status == GIT_DELTA_UNTRACKED &&
- (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
- return true;
-
- return false;
-}
-
-
-int git_diff_merge(
- git_diff_list *onto,
- const git_diff_list *from)
+ const git_diff_options *opts)
{
int error = 0;
- git_pool onto_pool;
- git_vector onto_new;
- git_diff_delta *delta;
- bool ignore_case = false;
- unsigned int i, j;
-
- assert(onto && from);
- if (!from->deltas.length)
- return 0;
-
- if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 ||
- git_pool_init(&onto_pool, 1, 0) < 0)
- return -1;
+ assert(diff && repo);
- if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ||
- (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)
- {
- ignore_case = true;
-
- /* This function currently only supports merging diff lists that
- * are sorted identically. */
- assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
- (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0);
- }
-
- for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
- git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
- const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
- int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
-
- if (cmp < 0) {
- delta = diff_delta__dup(o, &onto_pool);
- i++;
- } else if (cmp > 0) {
- delta = diff_delta__dup(f, &onto_pool);
- j++;
- } else {
- delta = diff_delta__merge_like_cgit(o, f, &onto_pool);
- i++;
- j++;
- }
-
- /* the ignore rules for the target may not match the source
- * or the result of a merged delta could be skippable...
- */
- if (git_diff_delta__should_skip(&onto->opts, delta)) {
- git__free(delta);
- continue;
- }
-
- if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
- break;
- }
-
- if (!error) {
- git_vector_swap(&onto->deltas, &onto_new);
- git_pool_swap(&onto->pool, &onto_pool);
- onto->new_src = from->new_src;
-
- /* prefix strings also come from old pool, so recreate those.*/
- onto->opts.old_prefix =
- git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix);
- onto->opts.new_prefix =
- git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix);
- }
-
- git_vector_foreach(&onto_new, i, delta)
- git__free(delta);
- git_vector_free(&onto_new);
- git_pool_clear(&onto_pool);
+ DIFF_FROM_ITERATORS(
+ git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx),
+ git_iterator_for_workdir_range(&b, repo, pfx, pfx)
+ );
return error;
}
diff --git a/src/diff.h b/src/diff.h
index c6a26aee7..1e3be7593 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -28,6 +28,9 @@ enum {
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
+#define GIT_DELTA__TO_DELETE 10
+#define GIT_DELTA__TO_SPLIT 11
+
struct git_diff_list {
git_refcount rc;
git_repository *repo;
@@ -38,6 +41,11 @@ struct git_diff_list {
git_iterator_type_t old_src;
git_iterator_type_t new_src;
uint32_t diffcaps;
+
+ int (*strcomp)(const char *, const char *);
+ int (*strncomp)(const char *, const char *, size_t);
+ int (*pfxcomp)(const char *str, const char *pfx);
+ int (*entrycomp)(const void *a, const void *b);
};
extern void git_diff__cleanup_modes(
@@ -45,8 +53,13 @@ extern void git_diff__cleanup_modes(
extern void git_diff_list_addref(git_diff_list *diff);
+extern int git_diff_delta__cmp(const void *a, const void *b);
+
extern bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta);
+extern int git_diff__oid_for_file(
+ git_repository *, const char *, uint16_t, git_off_t, git_oid *);
+
#endif
diff --git a/src/diff_output.c b/src/diff_output.c
index 5f0d13c64..46a9e02bf 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -120,7 +120,7 @@ static int diff_delta_is_binary_by_attr(
return -1;
mirror_new = (delta->new_file.path == delta->old_file.path ||
- strcmp(delta->new_file.path, delta->old_file.path) == 0);
+ ctxt->diff->strcomp(delta->new_file.path, delta->old_file.path) == 0);
if (mirror_new)
delta->new_file.flags |= (delta->old_file.flags & KNOWN_BINARY_FLAGS);
else
@@ -1002,7 +1002,7 @@ static int print_compact(
git_buf_clear(pi->buf);
if (delta->old_file.path != delta->new_file.path &&
- strcmp(delta->old_file.path,delta->new_file.path) != 0)
+ pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0)
git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code,
delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (delta->old_file.mode != delta->new_file.mode &&
@@ -1502,3 +1502,129 @@ notfound:
return GIT_ENOTFOUND;
}
+static int print_to_buffer_cb(
+ void *cb_data,
+ const git_diff_delta *delta,
+ const git_diff_range *range,
+ char line_origin,
+ const char *content,
+ size_t content_len)
+{
+ git_buf *output = cb_data;
+ GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin);
+ return git_buf_put(output, content, content_len);
+}
+
+int git_diff_patch_print(
+ git_diff_patch *patch,
+ void *cb_data,
+ git_diff_data_fn print_cb)
+{
+ int error;
+ git_buf temp = GIT_BUF_INIT;
+ diff_print_info pi;
+ size_t h, l;
+
+ assert(patch && print_cb);
+
+ pi.diff = patch->diff;
+ pi.print_cb = print_cb;
+ pi.cb_data = cb_data;
+ pi.buf = &temp;
+
+ error = print_patch_file(&pi, patch->delta, 0);
+
+ for (h = 0; h < patch->hunks_size && !error; ++h) {
+ diff_patch_hunk *hunk = &patch->hunks[h];
+
+ error = print_patch_hunk(&pi, patch->delta,
+ &hunk->range, hunk->header, hunk->header_len);
+
+ for (l = 0; l < hunk->line_count && !error; ++l) {
+ diff_patch_line *line = &patch->lines[hunk->line_start + l];
+
+ error = print_patch_line(
+ &pi, patch->delta, &hunk->range,
+ line->origin, line->ptr, line->len);
+ }
+ }
+
+ git_buf_free(&temp);
+
+ return error;
+}
+
+int git_diff_patch_to_str(
+ char **string,
+ git_diff_patch *patch)
+{
+ int error;
+ git_buf output = GIT_BUF_INIT;
+
+ error = git_diff_patch_print(patch, &output, print_to_buffer_cb);
+
+ /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1,
+ * meaning a memory allocation failure, so just map to -1...
+ */
+ if (error == GIT_EUSER)
+ error = -1;
+
+ *string = git_buf_detach(&output);
+
+ return error;
+}
+
+int git_diff__paired_foreach(
+ git_diff_list *idx2head,
+ git_diff_list *wd2idx,
+ int (*cb)(void *cbref, git_diff_delta *i2h, git_diff_delta *w2i),
+ void *cbref)
+{
+ int cmp;
+ git_diff_delta *i2h, *w2i;
+ size_t i, j, i_max, j_max;
+ bool icase = false;
+
+ i_max = idx2head ? idx2head->deltas.length : 0;
+ j_max = wd2idx ? wd2idx->deltas.length : 0;
+
+ if (idx2head && wd2idx &&
+ (0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) ||
+ 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)))
+ {
+ /* Then use the ignore-case sorter... */
+ icase = true;
+
+ /* and assert that both are ignore-case sorted. If this function
+ * ever needs to support merge joining result sets that are not sorted
+ * by the same function, then it will need to be extended to do a spool
+ * and sort on one of the results before merge joining */
+ assert(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) &&
+ 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE));
+ }
+
+ for (i = 0, j = 0; i < i_max || j < j_max; ) {
+ i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
+ w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;
+
+ cmp = !w2i ? -1 : !i2h ? 1 :
+ STRCMP_CASESELECT(icase, i2h->old_file.path, w2i->old_file.path);
+
+ if (cmp < 0) {
+ if (cb(cbref, i2h, NULL))
+ return GIT_EUSER;
+ i++;
+ } else if (cmp > 0) {
+ if (cb(cbref, NULL, w2i))
+ return GIT_EUSER;
+ j++;
+ } else {
+ if (cb(cbref, i2h, w2i))
+ return GIT_EUSER;
+ i++; j++;
+ }
+ }
+
+ return 0;
+}
+
diff --git a/src/diff_output.h b/src/diff_output.h
index 5fed1d998..f74dd3a71 100644
--- a/src/diff_output.h
+++ b/src/diff_output.h
@@ -83,4 +83,10 @@ typedef struct {
uint32_t diffed : 1;
} diff_delta_context;
+extern int git_diff__paired_foreach(
+ git_diff_list *idx2head,
+ git_diff_list *wd2idx,
+ int (*cb)(void *cbref, git_diff_delta *i2h, git_diff_delta *w2i),
+ void *cbref);
+
#endif
diff --git a/src/diff_tform.c b/src/diff_tform.c
new file mode 100644
index 000000000..987d4b8e6
--- /dev/null
+++ b/src/diff_tform.c
@@ -0,0 +1,466 @@
+/*
+ * Copyright (C) 2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+#include "common.h"
+#include "diff.h"
+#include "git2/config.h"
+
+static git_diff_delta *diff_delta__dup(
+ const git_diff_delta *d, git_pool *pool)
+{
+ git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
+ if (!delta)
+ return NULL;
+
+ memcpy(delta, d, sizeof(git_diff_delta));
+
+ delta->old_file.path = git_pool_strdup(pool, d->old_file.path);
+ if (delta->old_file.path == NULL)
+ goto fail;
+
+ if (d->new_file.path != d->old_file.path) {
+ delta->new_file.path = git_pool_strdup(pool, d->new_file.path);
+ if (delta->new_file.path == NULL)
+ goto fail;
+ } else {
+ delta->new_file.path = delta->old_file.path;
+ }
+
+ return delta;
+
+fail:
+ git__free(delta);
+ return NULL;
+}
+
+static git_diff_delta *diff_delta__merge_like_cgit(
+ const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
+{
+ git_diff_delta *dup;
+
+ /* Emulate C git for merging two diffs (a la 'git diff <sha>').
+ *
+ * When C git does a diff between the work dir and a tree, it actually
+ * diffs with the index but uses the workdir contents. This emulates
+ * those choices so we can emulate the type of diff.
+ *
+ * We have three file descriptions here, let's call them:
+ * f1 = a->old_file
+ * f2 = a->new_file AND b->old_file
+ * f3 = b->new_file
+ */
+
+ /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
+ if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
+ return diff_delta__dup(a, pool);
+
+ /* otherwise, base this diff on the 'b' diff */
+ if ((dup = diff_delta__dup(b, pool)) == NULL)
+ return NULL;
+
+ /* If 'a' status is uninteresting, then we're done */
+ if (a->status == GIT_DELTA_UNMODIFIED)
+ return dup;
+
+ assert(a->status != GIT_DELTA_UNMODIFIED);
+ assert(b->status != GIT_DELTA_UNMODIFIED);
+
+ /* A cgit exception is that the diff of a file that is only in the
+ * index (i.e. not in HEAD nor workdir) is given as empty.
+ */
+ if (dup->status == GIT_DELTA_DELETED) {
+ if (a->status == GIT_DELTA_ADDED)
+ dup->status = GIT_DELTA_UNMODIFIED;
+ /* else don't overwrite DELETE status */
+ } else {
+ dup->status = a->status;
+ }
+
+ git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);
+ dup->old_file.mode = a->old_file.mode;
+ dup->old_file.size = a->old_file.size;
+ dup->old_file.flags = a->old_file.flags;
+
+ return dup;
+}
+
+int git_diff_merge(
+ git_diff_list *onto,
+ const git_diff_list *from)
+{
+ int error = 0;
+ git_pool onto_pool;
+ git_vector onto_new;
+ git_diff_delta *delta;
+ bool ignore_case = false;
+ unsigned int i, j;
+
+ assert(onto && from);
+
+ if (!from->deltas.length)
+ return 0;
+
+ if (git_vector_init(
+ &onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 ||
+ git_pool_init(&onto_pool, 1, 0) < 0)
+ return -1;
+
+ if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ||
+ (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)
+ {
+ ignore_case = true;
+
+ /* This function currently only supports merging diff lists that
+ * are sorted identically. */
+ assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
+ (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0);
+ }
+
+ for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) {
+ git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i);
+ const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j);
+ int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
+
+ if (cmp < 0) {
+ delta = diff_delta__dup(o, &onto_pool);
+ i++;
+ } else if (cmp > 0) {
+ delta = diff_delta__dup(f, &onto_pool);
+ j++;
+ } else {
+ delta = diff_delta__merge_like_cgit(o, f, &onto_pool);
+ i++;
+ j++;
+ }
+
+ /* the ignore rules for the target may not match the source
+ * or the result of a merged delta could be skippable...
+ */
+ if (git_diff_delta__should_skip(&onto->opts, delta)) {
+ git__free(delta);
+ continue;
+ }
+
+ if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
+ break;
+ }
+
+ if (!error) {
+ git_vector_swap(&onto->deltas, &onto_new);
+ git_pool_swap(&onto->pool, &onto_pool);
+ onto->new_src = from->new_src;
+
+ /* prefix strings also come from old pool, so recreate those.*/
+ onto->opts.old_prefix =
+ git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix);
+ onto->opts.new_prefix =
+ git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix);
+ }
+
+ git_vector_foreach(&onto_new, i, delta)
+ git__free(delta);
+ git_vector_free(&onto_new);
+ git_pool_clear(&onto_pool);
+
+ return error;
+}
+
+#define DEFAULT_THRESHOLD 50
+#define DEFAULT_BREAK_REWRITE_THRESHOLD 60
+#define DEFAULT_TARGET_LIMIT 200
+
+static int normalize_find_opts(
+ git_diff_list *diff,
+ git_diff_find_options *opts,
+ git_diff_find_options *given)
+{
+ git_config *cfg = NULL;
+ const char *val;
+
+ if (diff->repo != NULL &&
+ git_repository_config__weakptr(&cfg, diff->repo) < 0)
+ return -1;
+
+ if (given != NULL)
+ memcpy(opts, given, sizeof(*opts));
+ else {
+ memset(opts, 0, sizeof(*opts));
+
+ opts->flags = GIT_DIFF_FIND_RENAMES;
+
+ if (git_config_get_string(&val, cfg, "diff.renames") < 0)
+ giterr_clear();
+ else if (val &&
+ (!strcasecmp(val, "copies") || !strcasecmp(val, "copy")))
+ opts->flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
+ }
+
+ /* some flags imply others */
+
+ if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES)
+ opts->flags |= GIT_DIFF_FIND_RENAMES;
+
+ if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)
+ opts->flags |= GIT_DIFF_FIND_COPIES;
+
+#define USE_DEFAULT(X) ((X) == 0 || (X) > 100)
+
+ if (USE_DEFAULT(opts->rename_threshold))
+ opts->rename_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->rename_from_rewrite_threshold))
+ opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->copy_threshold))
+ opts->copy_threshold = DEFAULT_THRESHOLD;
+
+ if (USE_DEFAULT(opts->break_rewrite_threshold))
+ opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD;
+
+#undef USE_DEFAULT
+
+ if (!opts->target_limit) {
+ int32_t limit = 0;
+
+ opts->target_limit = DEFAULT_TARGET_LIMIT;
+
+ if (git_config_get_int32(&limit, cfg, "diff.renameLimit") < 0)
+ giterr_clear();
+ else if (limit > 0)
+ opts->target_limit = limit;
+ }
+
+ return 0;
+}
+
+static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size)
+{
+ git_vector onto = GIT_VECTOR_INIT;
+ size_t i;
+ git_diff_delta *delta;
+
+ if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0)
+ return -1;
+
+ /* build new delta list without TO_DELETE and splitting TO_SPLIT */
+ git_vector_foreach(&diff->deltas, i, delta) {
+ if (delta->status == GIT_DELTA__TO_DELETE) {
+ git__free(delta);
+ continue;
+ }
+
+ if (delta->status == GIT_DELTA__TO_SPLIT) {
+ git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);
+ if (!deleted)
+ return -1;
+
+ deleted->status = GIT_DELTA_DELETED;
+ memset(&deleted->new_file, 0, sizeof(deleted->new_file));
+ deleted->new_file.path = deleted->old_file.path;
+ deleted->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
+
+ git_vector_insert(&onto, deleted);
+
+ delta->status = GIT_DELTA_ADDED;
+ memset(&delta->old_file, 0, sizeof(delta->old_file));
+ delta->old_file.path = delta->new_file.path;
+ delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
+ }
+
+ git_vector_insert(&onto, delta);
+ }
+
+ /* swap new delta list into place */
+ git_vector_sort(&onto);
+ git_vector_swap(&diff->deltas, &onto);
+ git_vector_free(&onto);
+
+ return 0;
+}
+
+static unsigned int calc_similarity(
+ void *cache, git_diff_file *old_file, git_diff_file *new_file)
+{
+ GIT_UNUSED(cache);
+
+ if (git_oid_cmp(&old_file->oid, &new_file->oid) == 0)
+ return 100;
+
+ /* TODO: insert actual similarity algo here */
+
+ return 0;
+}
+
+#define FLAG_SET(opts,flag_name) ((opts.flags & flag_name) != 0)
+
+int git_diff_find_similar(
+ git_diff_list *diff,
+ git_diff_find_options *given_opts)
+{
+ unsigned int i, j, similarity;
+ git_diff_delta *from, *to;
+ git_diff_find_options opts;
+ unsigned int tried_targets, num_changes = 0;
+ git_vector matches = GIT_VECTOR_INIT;
+
+ if (normalize_find_opts(diff, &opts, given_opts) < 0)
+ return -1;
+
+ /* first do splits if requested */
+
+ if (FLAG_SET(opts, GIT_DIFF_FIND_AND_BREAK_REWRITES)) {
+ git_vector_foreach(&diff->deltas, i, from) {
+ if (from->status != GIT_DELTA_MODIFIED)
+ continue;
+
+ /* Right now, this doesn't work right because the similarity
+ * algorithm isn't actually implemented...
+ */
+ similarity = 100;
+ /* calc_similarity(NULL, &from->old_file, from->new_file); */
+
+ if (similarity < opts.break_rewrite_threshold) {
+ from->status = GIT_DELTA__TO_SPLIT;
+ num_changes++;
+ }
+ }
+
+ /* apply splits as needed */
+ if (num_changes > 0 &&
+ apply_splits_and_deletes(
+ diff, diff->deltas.length + num_changes) < 0)
+ return -1;
+ }
+
+ /* next find the most similar delta for each rename / copy candidate */
+
+ if (git_vector_init(&matches, diff->deltas.length, git_diff_delta__cmp) < 0)
+ return -1;
+
+ git_vector_foreach(&diff->deltas, i, from) {
+ tried_targets = 0;
+
+ git_vector_foreach(&diff->deltas, j, to) {
+ if (i == j)
+ continue;
+
+ switch (to->status) {
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_UNTRACKED:
+ case GIT_DELTA_RENAMED:
+ case GIT_DELTA_COPIED:
+ break;
+ default:
+ /* only the above status values should be checked */
+ continue;
+ }
+
+ /* skip all but DELETED files unless copy detection is on */
+ if (from->status != GIT_DELTA_DELETED &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_COPIES))
+ continue;
+
+ /* don't check UNMODIFIED files as source unless given option */
+ if (from->status == GIT_DELTA_UNMODIFIED &&
+ !FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED))
+ continue;
+
+ /* cap on maximum files we'll examine */
+ if (++tried_targets > opts.target_limit)
+ break;
+
+ /* calculate similarity and see if this pair beats the
+ * similarity score of the current best pair.
+ */
+ similarity = calc_similarity(NULL, &from->old_file, &to->new_file);
+
+ if (to->similarity < similarity) {
+ to->similarity = similarity;
+ if (git_vector_set(NULL, &matches, j, from) < 0)
+ return -1;
+ }
+ }
+ }
+
+ /* next rewrite the diffs with renames / copies */
+
+ num_changes = 0;
+
+ git_vector_foreach(&diff->deltas, j, to) {
+ from = GIT_VECTOR_GET(&matches, j);
+ if (!from) {
+ assert(to->similarity == 0);
+ continue;
+ }
+
+ /* three possible outcomes here:
+ * 1. old DELETED and if over rename threshold,
+ * new becomes RENAMED and old goes away
+ * 2. old was MODIFIED but FIND_RENAMES_FROM_REWRITES is on and
+ * old is more similar to new than it is to itself, in which
+ * case, new becomes RENAMED and old becomed ADDED
+ * 3. otherwise if over copy threshold, new becomes COPIED
+ */
+
+ if (from->status == GIT_DELTA_DELETED) {
+ if (to->similarity < opts.rename_threshold) {
+ to->similarity = 0;
+ continue;
+ }
+
+ to->status = GIT_DELTA_RENAMED;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+
+ from->status = GIT_DELTA__TO_DELETE;
+ num_changes++;
+
+ continue;
+ }
+
+ if (from->status == GIT_DELTA_MODIFIED &&
+ FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) &&
+ to->similarity > opts.rename_threshold)
+ {
+ similarity = 100;
+ /* calc_similarity(NULL, &from->old_file, from->new_file); */
+
+ if (similarity < opts.rename_from_rewrite_threshold) {
+ to->status = GIT_DELTA_RENAMED;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+
+ from->status = GIT_DELTA_ADDED;
+ memset(&from->old_file, 0, sizeof(from->old_file));
+ from->old_file.path = to->old_file.path;
+ from->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
+
+ continue;
+ }
+ }
+
+ if (to->similarity < opts.copy_threshold) {
+ to->similarity = 0;
+ continue;
+ }
+
+ /* convert "to" to a COPIED record */
+ to->status = GIT_DELTA_COPIED;
+ memcpy(&to->old_file, &from->old_file, sizeof(to->old_file));
+ }
+
+ git_vector_free(&matches);
+
+ if (num_changes > 0) {
+ assert(num_changes < diff->deltas.length);
+
+ if (apply_splits_and_deletes(
+ diff, diff->deltas.length - num_changes) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+#undef FLAG_SET
diff --git a/src/errors.c b/src/errors.c
index 942a2f799..ac7fa934d 100644
--- a/src/errors.c
+++ b/src/errors.c
@@ -40,52 +40,41 @@ void giterr_set(int error_class, const char *string, ...)
{
git_buf buf = GIT_BUF_INIT;
va_list arglist;
-
- int unix_error_code = 0;
-
#ifdef GIT_WIN32
- DWORD win32_error_code = 0;
+ DWORD win32_error_code = (error_class == GITERR_OS) ? GetLastError() : 0;
#endif
-
- if (error_class == GITERR_OS) {
- unix_error_code = errno;
- errno = 0;
-
-#ifdef GIT_WIN32
- win32_error_code = GetLastError();
- SetLastError(0);
-#endif
- }
+ int error_code = (error_class == GITERR_OS) ? errno : 0;
va_start(arglist, string);
git_buf_vprintf(&buf, string, arglist);
va_end(arglist);
- /* automatically suffix strerror(errno) for GITERR_OS errors */
if (error_class == GITERR_OS) {
-
- if (unix_error_code != 0) {
- git_buf_PUTS(&buf, ": ");
- git_buf_puts(&buf, strerror(unix_error_code));
- }
-
#ifdef GIT_WIN32
- else if (win32_error_code != 0) {
- LPVOID lpMsgBuf = NULL;
-
- FormatMessage(
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, win32_error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL);
-
- if (lpMsgBuf) {
+ if (win32_error_code) {
+ char *lpMsgBuf;
+
+ if (FormatMessageA(
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, win32_error_code, 0, (LPSTR)&lpMsgBuf, 0, NULL)) {
git_buf_PUTS(&buf, ": ");
git_buf_puts(&buf, lpMsgBuf);
LocalFree(lpMsgBuf);
}
+
+ SetLastError(0);
}
+ else
#endif
+ if (error_code) {
+ git_buf_PUTS(&buf, ": ");
+ git_buf_puts(&buf, strerror(error_code));
+ }
+
+ if (error_code)
+ errno = 0;
}
if (!git_buf_oom(&buf))
diff --git a/src/fetch.c b/src/fetch.c
index dc01f6791..81136fc5f 100644
--- a/src/fetch.c
+++ b/src/fetch.c
@@ -8,16 +8,14 @@
#include "git2/oid.h"
#include "git2/refs.h"
#include "git2/revwalk.h"
-#include "git2/indexer.h"
+#include "git2/transport.h"
#include "common.h"
-#include "transport.h"
#include "remote.h"
#include "refspec.h"
#include "pack.h"
#include "fetch.h"
#include "netops.h"
-#include "pkt.h"
struct filter_payload {
git_remote *remote;
@@ -86,61 +84,6 @@ cleanup:
return error;
}
-/* Wait until we get an ack from the */
-static int recv_pkt(git_pkt **out, gitno_buffer *buf)
-{
- const char *ptr = buf->data, *line_end = ptr;
- git_pkt *pkt;
- int pkt_type, error = 0, ret;
-
- do {
- if (buf->offset > 0)
- error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
- else
- error = GIT_EBUFS;
-
- if (error == 0)
- break; /* return the pkt */
-
- if (error < 0 && error != GIT_EBUFS)
- return -1;
-
- if ((ret = gitno_recv(buf)) < 0)
- return -1;
- } while (error);
-
- gitno_consume(buf, line_end);
- pkt_type = pkt->type;
- if (out != NULL)
- *out = pkt;
- else
- git__free(pkt);
-
- return pkt_type;
-}
-
-static int store_common(git_transport *t)
-{
- git_pkt *pkt = NULL;
- gitno_buffer *buf = &t->buffer;
-
- do {
- if (recv_pkt(&pkt, buf) < 0)
- return -1;
-
- if (pkt->type == GIT_PKT_ACK) {
- if (git_vector_insert(&t->common, pkt) < 0)
- return -1;
- } else {
- git__free(pkt);
- return 0;
- }
-
- } while (1);
-
- return 0;
-}
-
/*
* In this first version, we push all our refs in and start sending
* them out. When we get an ACK we hide that commit and continue
@@ -149,13 +92,7 @@ static int store_common(git_transport *t)
int git_fetch_negotiate(git_remote *remote)
{
git_transport *t = remote->transport;
- gitno_buffer *buf = &t->buffer;
- git_buf data = GIT_BUF_INIT;
- git_revwalk *walk = NULL;
- int error = -1, pkt_type;
- unsigned int i;
- git_oid oid;
-
+
if (filter_wants(remote) < 0) {
giterr_set(GITERR_NET, "Failed to filter the reference list for wants");
return -1;
@@ -167,298 +104,23 @@ int git_fetch_negotiate(git_remote *remote)
/*
* Now we have everything set up so we can start tell the
- * server what we want and what we have. Call the function if
- * the transport has its own logic. This is transitional and
- * will be removed once this function can support git and http.
- */
- if (t->own_logic)
- return t->negotiate_fetch(t, remote->repo, &remote->refs);
-
- /* No own logic, do our thing */
- if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
- return -1;
-
- if (git_fetch_setup_walk(&walk, remote->repo) < 0)
- goto on_error;
- /*
- * We don't support any kind of ACK extensions, so the negotiation
- * boils down to sending what we have and listening for an ACK
- * every once in a while.
+ * server what we want and what we have.
*/
- i = 0;
- while ((error = git_revwalk_next(&oid, walk)) == 0) {
- git_pkt_buffer_have(&oid, &data);
- i++;
- if (i % 20 == 0) {
- if (t->cancel.val) {
- giterr_set(GITERR_NET, "The fetch was cancelled by the user");
- error = GIT_EUSER;
- goto on_error;
- }
-
- git_pkt_buffer_flush(&data);
- if (git_buf_oom(&data))
- goto on_error;
-
- if (t->negotiation_step(t, data.ptr, data.size) < 0)
- goto on_error;
-
- git_buf_clear(&data);
- if (t->caps.multi_ack) {
- if (store_common(t) < 0)
- goto on_error;
- } else {
- pkt_type = recv_pkt(NULL, buf);
-
- if (pkt_type == GIT_PKT_ACK) {
- break;
- } else if (pkt_type == GIT_PKT_NAK) {
- continue;
- } else {
- giterr_set(GITERR_NET, "Unexpected pkt type");
- goto on_error;
- }
- }
- }
-
- if (t->common.length > 0)
- break;
-
- if (i % 20 == 0 && t->rpc) {
- git_pkt_ack *pkt;
- unsigned int i;
-
- if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
- goto on_error;
-
- git_vector_foreach(&t->common, i, pkt) {
- git_pkt_buffer_have(&pkt->oid, &data);
- }
-
- if (git_buf_oom(&data))
- goto on_error;
- }
- }
-
- if (error < 0 && error != GIT_ITEROVER)
- goto on_error;
-
- /* Tell the other end that we're done negotiating */
- if (t->rpc && t->common.length > 0) {
- git_pkt_ack *pkt;
- unsigned int i;
-
- if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0)
- goto on_error;
-
- git_vector_foreach(&t->common, i, pkt) {
- git_pkt_buffer_have(&pkt->oid, &data);
- }
-
- if (git_buf_oom(&data))
- goto on_error;
- }
-
- git_pkt_buffer_done(&data);
- if (t->cancel.val) {
- giterr_set(GITERR_NET, "The fetch was cancelled by the user");
- error = GIT_EUSER;
- goto on_error;
- }
- if (t->negotiation_step(t, data.ptr, data.size) < 0)
- goto on_error;
-
- git_buf_free(&data);
- git_revwalk_free(walk);
-
- /* Now let's eat up whatever the server gives us */
- if (!t->caps.multi_ack) {
- pkt_type = recv_pkt(NULL, buf);
- if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
- giterr_set(GITERR_NET, "Unexpected pkt type");
- return -1;
- }
- } else {
- git_pkt_ack *pkt;
- do {
- if (recv_pkt((git_pkt **)&pkt, buf) < 0)
- return -1;
-
- if (pkt->type == GIT_PKT_NAK ||
- (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {
- git__free(pkt);
- break;
- }
-
- git__free(pkt);
- } while (1);
- }
-
- return 0;
-
-on_error:
- git_revwalk_free(walk);
- git_buf_free(&data);
- return error;
+ return t->negotiate_fetch(t,
+ remote->repo,
+ (const git_remote_head * const *)remote->refs.contents,
+ remote->refs.length);
}
-int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats)
+int git_fetch_download_pack(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
{
git_transport *t = remote->transport;
if(!remote->need_pack)
return 0;
- if (t->own_logic)
- return t->download_pack(t, remote->repo, bytes, stats);
-
- return git_fetch__download_pack(t, remote->repo, bytes, stats);
-
-}
-
-static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer *buf, git_off_t *bytes, git_indexer_stats *stats)
-{
- int recvd;
-
- do {
- if (t->cancel.val) {
- giterr_set(GITERR_NET, "The fetch was cancelled by the user");
- return GIT_EUSER;
- }
-
- if (git_indexer_stream_add(idx, buf->data, buf->offset, stats) < 0)
- return -1;
-
- gitno_consume_n(buf, buf->offset);
-
- if ((recvd = gitno_recv(buf)) < 0)
- return -1;
-
- *bytes += recvd;
- } while(recvd > 0);
-
- if (git_indexer_stream_finalize(idx, stats))
- return -1;
-
- return 0;
-}
-
-/* Receiving data from a socket and storing it is pretty much the same for git and HTTP */
-int git_fetch__download_pack(
- git_transport *t,
- git_repository *repo,
- git_off_t *bytes,
- git_indexer_stats *stats)
-{
- git_buf path = GIT_BUF_INIT;
- gitno_buffer *buf = &t->buffer;
- git_indexer_stream *idx = NULL;
- int error = -1;
-
- if (git_buf_joinpath(&path, git_repository_path(repo), "objects/pack") < 0)
- return -1;
-
- if (git_indexer_stream_new(&idx, git_buf_cstr(&path)) < 0)
- goto on_error;
-
- git_buf_free(&path);
- memset(stats, 0, sizeof(git_indexer_stats));
- *bytes = 0;
-
- /*
- * If the remote doesn't support the side-band, we can feed
- * the data directly to the indexer. Otherwise, we need to
- * check which one belongs there.
- */
- if (!t->caps.side_band && !t->caps.side_band_64k) {
- if (no_sideband(t, idx, buf, bytes, stats) < 0)
- goto on_error;
-
- git_indexer_stream_free(idx);
- return 0;
- }
-
- do {
- git_pkt *pkt;
-
- if (t->cancel.val) {
- giterr_set(GITERR_NET, "The fetch was cancelled by the user");
- error = GIT_EUSER;
- goto on_error;
- }
-
- if (recv_pkt(&pkt, buf) < 0)
- goto on_error;
-
- if (pkt->type == GIT_PKT_PROGRESS) {
- if (t->progress_cb) {
- git_pkt_progress *p = (git_pkt_progress *) pkt;
- t->progress_cb(p->data, p->len, t->cb_data);
- }
- git__free(pkt);
- } else if (pkt->type == GIT_PKT_DATA) {
- git_pkt_data *p = (git_pkt_data *) pkt;
- *bytes += p->len;
- if (git_indexer_stream_add(idx, p->data, p->len, stats) < 0)
- goto on_error;
-
- git__free(pkt);
- } else if (pkt->type == GIT_PKT_FLUSH) {
- /* A flush indicates the end of the packfile */
- git__free(pkt);
- break;
- }
- } while (1);
-
- if (git_indexer_stream_finalize(idx, stats) < 0)
- goto on_error;
-
- git_indexer_stream_free(idx);
- return 0;
-
-on_error:
- git_buf_free(&path);
- git_indexer_stream_free(idx);
- return error;
-}
-
-int git_fetch_setup_walk(git_revwalk **out, git_repository *repo)
-{
- git_revwalk *walk;
- git_strarray refs;
- unsigned int i;
- git_reference *ref;
-
- if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)
- return -1;
-
- if (git_revwalk_new(&walk, repo) < 0)
- return -1;
-
- git_revwalk_sorting(walk, GIT_SORT_TIME);
-
- for (i = 0; i < refs.count; ++i) {
- /* No tags */
- if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
- continue;
-
- if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0)
- goto on_error;
-
- if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
- continue;
- if (git_revwalk_push(walk, git_reference_oid(ref)) < 0)
- goto on_error;
-
- git_reference_free(ref);
- }
-
- git_strarray_free(&refs);
- *out = walk;
- return 0;
-
-on_error:
- git_reference_free(ref);
- git_strarray_free(&refs);
- return -1;
+ return t->download_pack(t, remote->repo, &remote->stats, progress_cb, progress_payload);
}
diff --git a/src/fetch.h b/src/fetch.h
index 87bb43b07..5b8c20665 100644
--- a/src/fetch.h
+++ b/src/fetch.h
@@ -10,9 +10,19 @@
#include "netops.h"
int git_fetch_negotiate(git_remote *remote);
-int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats);
-int git_fetch__download_pack(git_transport *t, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats);
+int git_fetch_download_pack(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
+
+int git_fetch__download_pack(
+ git_transport *t,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
+
int git_fetch_setup_walk(git_revwalk **out, git_repository *repo);
#endif
diff --git a/src/fetchhead.c b/src/fetchhead.c
new file mode 100644
index 000000000..ed47bab48
--- /dev/null
+++ b/src/fetchhead.c
@@ -0,0 +1,126 @@
+/*
+ * 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/types.h"
+#include "git2/oid.h"
+
+#include "fetchhead.h"
+#include "common.h"
+#include "fileops.h"
+#include "filebuf.h"
+#include "refs.h"
+#include "repository.h"
+
+
+int git_fetchhead_ref_cmp(const void *a, const void *b)
+{
+ const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;
+ const git_fetchhead_ref *two = (const git_fetchhead_ref *)b;
+
+ if (one->is_merge && !two->is_merge)
+ return -1;
+ if (two->is_merge && !one->is_merge)
+ return 1;
+
+ return strcmp(one->ref_name, two->ref_name);
+}
+
+int git_fetchhead_ref_create(
+ git_fetchhead_ref **fetchhead_ref_out,
+ git_oid *oid,
+ int is_merge,
+ const char *ref_name,
+ const char *remote_url)
+{
+ git_fetchhead_ref *fetchhead_ref = NULL;
+
+ assert(fetchhead_ref_out && oid && ref_name && remote_url);
+
+ fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));
+ GITERR_CHECK_ALLOC(fetchhead_ref);
+
+ memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));
+
+ git_oid_cpy(&fetchhead_ref->oid, oid);
+ fetchhead_ref->is_merge = is_merge;
+ fetchhead_ref->ref_name = git__strdup(ref_name);
+ fetchhead_ref->remote_url = git__strdup(remote_url);
+
+ *fetchhead_ref_out = fetchhead_ref;
+
+ return 0;
+}
+
+static int fetchhead_ref_write(
+ git_filebuf *file,
+ git_fetchhead_ref *fetchhead_ref)
+{
+ char oid[GIT_OID_HEXSZ + 1];
+ const char *type, *name;
+
+ assert(file && fetchhead_ref);
+
+ git_oid_fmt(oid, &fetchhead_ref->oid);
+ oid[GIT_OID_HEXSZ] = '\0';
+
+ if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) {
+ type = "branch ";
+ name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR);
+ } else if(git__prefixcmp(fetchhead_ref->ref_name,
+ GIT_REFS_TAGS_DIR) == 0) {
+ type = "tag ";
+ name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR);
+ } else {
+ type = "";
+ name = fetchhead_ref->ref_name;
+ }
+
+ return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n",
+ oid,
+ (fetchhead_ref->is_merge) ? "" : "not-for-merge",
+ type,
+ name,
+ fetchhead_ref->remote_url);
+}
+
+int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
+{
+ git_filebuf file = GIT_FILEBUF_INIT;
+ git_buf path = GIT_BUF_INIT;
+ unsigned int i;
+ git_fetchhead_ref *fetchhead_ref;
+
+ assert(repo && fetchhead_refs);
+
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_FETCH_HEAD_FILE) < 0)
+ return -1;
+
+ if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE) < 0) {
+ git_buf_free(&path);
+ return -1;
+ }
+
+ git_buf_free(&path);
+
+ git_vector_sort(fetchhead_refs);
+
+ git_vector_foreach(fetchhead_refs, i, fetchhead_ref)
+ fetchhead_ref_write(&file, fetchhead_ref);
+
+ return git_filebuf_commit(&file, GIT_REFS_FILE_MODE);
+}
+
+void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)
+{
+ if (fetchhead_ref == NULL)
+ return;
+
+ git__free(fetchhead_ref->remote_url);
+ git__free(fetchhead_ref->ref_name);
+ git__free(fetchhead_ref);
+}
+
diff --git a/src/fetchhead.h b/src/fetchhead.h
new file mode 100644
index 000000000..ec7c1985b
--- /dev/null
+++ b/src/fetchhead.h
@@ -0,0 +1,27 @@
+/*
+ * 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_fetchhead_h__
+#define INCLUDE_fetchhead_h__
+
+#include "vector.h"
+
+typedef struct git_fetchhead_ref {
+ git_oid oid;
+ unsigned int is_merge;
+ char *ref_name;
+ char *remote_url;
+} git_fetchhead_ref;
+
+int git_fetchhead_ref_create(git_fetchhead_ref **fetchhead_ref_out, git_oid *oid, int is_merge, const char *ref_name, const char *remote_url);
+
+int git_fetchhead_ref_cmp(const void *a, const void *b);
+
+int git_fetchhead_write(git_repository *repository, git_vector *fetchhead_refs);
+
+void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref);
+
+#endif
diff --git a/src/filebuf.c b/src/filebuf.c
index b9b908c8d..0eb5b458a 100644
--- a/src/filebuf.c
+++ b/src/filebuf.c
@@ -85,8 +85,8 @@ static int lock_file(git_filebuf *file, int flags)
while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) {
p_write(file->fd, buffer, read_bytes);
- if (file->digest)
- git_hash_update(file->digest, buffer, read_bytes);
+ if (file->compute_digest)
+ git_hash_update(&file->digest, buffer, read_bytes);
}
p_close(source);
@@ -108,8 +108,10 @@ void git_filebuf_cleanup(git_filebuf *file)
if (file->fd_is_open && file->path_lock && git_path_exists(file->path_lock))
p_unlink(file->path_lock);
- if (file->digest)
- git_hash_free_ctx(file->digest);
+ if (file->compute_digest) {
+ git_hash_ctx_cleanup(&file->digest);
+ file->compute_digest = 0;
+ }
if (file->buffer)
git__free(file->buffer);
@@ -149,8 +151,8 @@ static int write_normal(git_filebuf *file, void *source, size_t len)
return -1;
}
- if (file->digest)
- git_hash_update(file->digest, source, len);
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
}
return 0;
@@ -186,8 +188,8 @@ static int write_deflate(git_filebuf *file, void *source, size_t len)
assert(zs->avail_in == 0);
- if (file->digest)
- git_hash_update(file->digest, source, len);
+ if (file->compute_digest)
+ git_hash_update(&file->digest, source, len);
}
return 0;
@@ -221,8 +223,10 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags)
/* If we are hashing on-write, allocate a new hash context */
if (flags & GIT_FILEBUF_HASH_CONTENTS) {
- file->digest = git_hash_new_ctx();
- GITERR_CHECK_ALLOC(file->digest);
+ file->compute_digest = 1;
+
+ if (git_hash_ctx_init(&file->digest) < 0)
+ goto cleanup;
}
compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;
@@ -291,16 +295,16 @@ cleanup:
int git_filebuf_hash(git_oid *oid, git_filebuf *file)
{
- assert(oid && file && file->digest);
+ assert(oid && file && file->compute_digest);
flush_buffer(file);
if (verify_last_error(file) < 0)
return -1;
- git_hash_final(oid, file->digest);
- git_hash_free_ctx(file->digest);
- file->digest = NULL;
+ git_hash_final(oid, &file->digest);
+ git_hash_ctx_cleanup(&file->digest);
+ file->compute_digest = 0;
return 0;
}
@@ -466,3 +470,26 @@ int git_filebuf_printf(git_filebuf *file, const char *format, ...)
return res;
}
+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file)
+{
+ int res;
+ struct stat st;
+
+ if (file->fd_is_open)
+ res = p_fstat(file->fd, &st);
+ else
+ res = p_stat(file->path_original, &st);
+
+ if (res < 0) {
+ giterr_set(GITERR_OS, "Could not get stat info for '%s'",
+ file->path_original);
+ return res;
+ }
+
+ if (mtime)
+ *mtime = st.st_mtime;
+ if (size)
+ *size = (size_t)st.st_size;
+
+ return 0;
+}
diff --git a/src/filebuf.h b/src/filebuf.h
index 377883147..dcaad9bd8 100644
--- a/src/filebuf.h
+++ b/src/filebuf.h
@@ -31,7 +31,8 @@ struct git_filebuf {
int (*write)(struct git_filebuf *file, void *source, size_t len);
- git_hash_ctx *digest;
+ bool compute_digest;
+ git_hash_ctx digest;
unsigned char *buffer;
unsigned char *z_buf;
@@ -82,5 +83,6 @@ int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode);
void git_filebuf_cleanup(git_filebuf *lock);
int git_filebuf_hash(git_oid *oid, git_filebuf *file);
int git_filebuf_flush(git_filebuf *file);
+int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file);
#endif
diff --git a/src/fileops.c b/src/fileops.c
index 6342b1679..7f023bf69 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -14,7 +14,8 @@
int git_futils_mkpath2file(const char *file_path, const mode_t mode)
{
return git_futils_mkdir(
- file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST);
+ file_path, NULL, mode,
+ GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR);
}
int git_futils_mktmp(git_buf *path_out, const char *filename)
@@ -142,10 +143,11 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len)
}
int git_futils_readbuffer_updated(
- git_buf *buf, const char *path, time_t *mtime, int *updated)
+ git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated)
{
git_file fd;
struct stat st;
+ bool changed = false;
assert(buf && path && *path);
@@ -162,16 +164,25 @@ int git_futils_readbuffer_updated(
}
/*
- * If we were given a time, we only want to read the file if it
- * has been modified.
+ * If we were given a time and/or a size, we only want to read the file
+ * if it has been modified.
*/
- if (mtime != NULL && *mtime >= st.st_mtime) {
+ if (size && *size != (size_t)st.st_size)
+ changed = true;
+ if (mtime && *mtime != st.st_mtime)
+ changed = true;
+ if (!size && !mtime)
+ changed = true;
+
+ if (!changed) {
p_close(fd);
return 0;
}
if (mtime != NULL)
*mtime = st.st_mtime;
+ if (size != NULL)
+ *size = (size_t)st.st_size;
if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) {
p_close(fd);
@@ -188,7 +199,7 @@ int git_futils_readbuffer_updated(
int git_futils_readbuffer(git_buf *buf, const char *path)
{
- return git_futils_readbuffer_updated(buf, path, NULL, NULL);
+ return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL);
}
int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode)
@@ -240,6 +251,7 @@ int git_futils_mkdir(
mode_t mode,
uint32_t flags)
{
+ int error = -1;
git_buf make_path = GIT_BUF_INIT;
ssize_t root = 0;
char lastch, *tail;
@@ -287,12 +299,28 @@ int git_futils_mkdir(
*tail = '\0';
/* make directory */
- if (p_mkdir(make_path.ptr, mode) < 0 &&
- (errno != EEXIST || (flags & GIT_MKDIR_EXCL) != 0))
- {
- giterr_set(GITERR_OS, "Failed to make directory '%s'",
- make_path.ptr);
- goto fail;
+ if (p_mkdir(make_path.ptr, mode) < 0) {
+ if (errno == EEXIST) {
+ if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0) {
+ if (!git_path_isdir(make_path.ptr)) {
+ giterr_set(
+ GITERR_OS, "Existing path is not a directory '%s'",
+ make_path.ptr);
+ error = GIT_ENOTFOUND;
+ goto fail;
+ }
+ }
+ if ((flags & GIT_MKDIR_EXCL) != 0) {
+ giterr_set(GITERR_OS, "Directory already exists '%s'",
+ make_path.ptr);
+ error = GIT_EEXISTS;
+ goto fail;
+ }
+ } else {
+ giterr_set(GITERR_OS, "Failed to make directory '%s'",
+ make_path.ptr);
+ goto fail;
+ }
}
/* chmod if requested */
@@ -314,7 +342,7 @@ int git_futils_mkdir(
fail:
git_buf_free(&make_path);
- return -1;
+ return error;
}
int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
@@ -322,57 +350,145 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode)
return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH);
}
-static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
+typedef struct {
+ const char *base;
+ uint32_t flags;
+ int error;
+} futils__rmdir_data;
+
+static int futils__error_cannot_rmdir(const char *path, const char *filemsg)
{
- git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;
+ if (filemsg)
+ giterr_set(GITERR_OS, "Could not remove directory. File '%s' %s",
+ path, filemsg);
+ else
+ giterr_set(GITERR_OS, "Could not remove directory '%s'", path);
- if (git_path_isdir(path->ptr) == true) {
- if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
- return -1;
+ return -1;
+}
- if (p_rmdir(path->ptr) < 0) {
- if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST))
- return 0;
+static int futils__rm_first_parent(git_buf *path, const char *ceiling)
+{
+ int error = GIT_ENOTFOUND;
+ struct stat st;
- giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr);
- return -1;
- }
+ while (error == GIT_ENOTFOUND) {
+ git_buf_rtruncate_at_char(path, '/');
+
+ if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0)
+ error = 0;
+ else if (p_lstat_posixly(path->ptr, &st) == 0) {
+ if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))
+ error = p_unlink(path->ptr);
+ else if (!S_ISDIR(st.st_mode))
+ error = -1; /* fail to remove non-regular file */
+ } else if (errno != ENOTDIR)
+ error = -1;
+ }
- return 0;
+ if (error)
+ futils__error_cannot_rmdir(path->ptr, "cannot remove parent");
+
+ return error;
+}
+
+static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path)
+{
+ struct stat st;
+ futils__rmdir_data *data = opaque;
+
+ if ((data->error = p_lstat_posixly(path->ptr, &st)) < 0) {
+ if (errno == ENOENT)
+ data->error = 0;
+ else if (errno == ENOTDIR) {
+ /* asked to remove a/b/c/d/e and a/b is a normal file */
+ if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0)
+ data->error = futils__rm_first_parent(path, data->base);
+ else
+ futils__error_cannot_rmdir(
+ path->ptr, "parent is not directory");
+ }
+ else
+ futils__error_cannot_rmdir(path->ptr, "cannot access");
}
- if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) {
- if (p_unlink(path->ptr) < 0) {
- giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr);
- return -1;
+ else if (S_ISDIR(st.st_mode)) {
+ int error = git_path_direach(path, futils__rmdir_recurs_foreach, data);
+ if (error < 0)
+ return (error == GIT_EUSER) ? data->error : error;
+
+ data->error = p_rmdir(path->ptr);
+
+ if (data->error < 0) {
+ if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 &&
+ (errno == ENOTEMPTY || errno == EEXIST))
+ data->error = 0;
+ else
+ futils__error_cannot_rmdir(path->ptr, NULL);
}
+ }
- return 0;
+ else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) {
+ data->error = p_unlink(path->ptr);
+
+ if (data->error < 0)
+ futils__error_cannot_rmdir(path->ptr, "cannot be removed");
}
- if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) {
- giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr);
- return -1;
+ else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0)
+ data->error = futils__error_cannot_rmdir(path->ptr, "still present");
+
+ return data->error;
+}
+
+static int futils__rmdir_empty_parent(void *opaque, git_buf *path)
+{
+ int error = p_rmdir(path->ptr);
+
+ GIT_UNUSED(opaque);
+
+ if (error) {
+ int en = errno;
+
+ if (en == ENOENT || en == ENOTDIR) {
+ giterr_clear();
+ error = 0;
+ } else if (en == ENOTEMPTY || en == EEXIST) {
+ giterr_clear();
+ error = GIT_ITEROVER;
+ } else {
+ futils__error_cannot_rmdir(path->ptr, NULL);
+ }
}
- return 0;
+ return error;
}
int git_futils_rmdir_r(
- const char *path, const char *base, git_directory_removal_type removal_type)
+ const char *path, const char *base, uint32_t flags)
{
int error;
git_buf fullpath = GIT_BUF_INIT;
-
- assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
- || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
- || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
+ futils__rmdir_data data;
/* build path and find "root" where we should start calling mkdir */
if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
return -1;
- error = _rmdir_recurs_foreach(&removal_type, &fullpath);
+ data.base = base ? base : "";
+ data.flags = flags;
+ data.error = 0;
+
+ error = futils__rmdir_recurs_foreach(&data, &fullpath);
+
+ /* remove now-empty parents if requested */
+ if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) {
+ error = git_path_walk_up(
+ &fullpath, base, futils__rmdir_empty_parent, &data);
+
+ if (error == GIT_ITEROVER)
+ error = 0;
+ }
git_buf_free(&fullpath);
@@ -660,3 +776,38 @@ int git_futils_cp_r(
return error;
}
+
+int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path)
+{
+ struct stat st;
+
+ /* if the stamp is NULL, then always reload */
+ if (stamp == NULL)
+ return 1;
+
+ if (p_stat(path, &st) < 0)
+ return GIT_ENOTFOUND;
+
+ if (stamp->mtime == (git_time_t)st.st_mtime &&
+ stamp->size == (git_off_t)st.st_size &&
+ stamp->ino == (unsigned int)st.st_ino)
+ return 0;
+
+ stamp->mtime = (git_time_t)st.st_mtime;
+ stamp->size = (git_off_t)st.st_size;
+ stamp->ino = (unsigned int)st.st_ino;
+
+ return 1;
+}
+
+void git_futils_filestamp_set(
+ git_futils_filestamp *target, const git_futils_filestamp *source)
+{
+ assert(target);
+
+ if (source)
+ memcpy(target, source, sizeof(*target));
+ else
+ memset(target, 0, sizeof(*target));
+}
diff --git a/src/fileops.h b/src/fileops.h
index 19f7ffd54..a74f8b758 100644
--- a/src/fileops.h
+++ b/src/fileops.h
@@ -18,7 +18,8 @@
* Read whole files into an in-memory buffer for processing
*/
extern int git_futils_readbuffer(git_buf *obj, const char *path);
-extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated);
+extern int git_futils_readbuffer_updated(
+ git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated);
extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
/**
@@ -64,6 +65,7 @@ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t m
* * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation
* * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path
* * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path
+ * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST
*
* Note that the chmod options will be executed even if the directory already
* exists, unless GIT_MKDIR_EXCL is given.
@@ -73,7 +75,8 @@ typedef enum {
GIT_MKDIR_PATH = 2,
GIT_MKDIR_CHMOD = 4,
GIT_MKDIR_CHMOD_PATH = 8,
- GIT_MKDIR_SKIP_LAST = 16
+ GIT_MKDIR_SKIP_LAST = 16,
+ GIT_MKDIR_VERIFY_DIR = 32,
} git_futils_mkdir_flags;
/**
@@ -97,27 +100,40 @@ extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uin
*/
extern int git_futils_mkpath2file(const char *path, const mode_t mode);
+/**
+ * Flags to pass to `git_futils_rmdir_r`.
+ *
+ * * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty
+ * dirs and generate error if any files are found.
+ * * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy.
+ * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error.
+ * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base
+ * if removing this item leaves them empty
+ * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR
+ *
+ * The old values translate into the new as follows:
+ *
+ * * GIT_DIRREMOVAL_EMPTY_HIERARCHY == GIT_RMDIR_EMPTY_HIERARCHY
+ * * GIT_DIRREMOVAL_FILES_AND_DIRS ~= GIT_RMDIR_REMOVE_FILES
+ * * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS == GIT_RMDIR_SKIP_NONEMPTY
+ */
typedef enum {
- GIT_DIRREMOVAL_EMPTY_HIERARCHY = 0,
- GIT_DIRREMOVAL_FILES_AND_DIRS = 1,
- GIT_DIRREMOVAL_ONLY_EMPTY_DIRS = 2,
-} git_directory_removal_type;
+ GIT_RMDIR_EMPTY_HIERARCHY = 0,
+ GIT_RMDIR_REMOVE_FILES = (1 << 0),
+ GIT_RMDIR_SKIP_NONEMPTY = (1 << 1),
+ GIT_RMDIR_EMPTY_PARENTS = (1 << 2),
+ GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3),
+} git_futils_rmdir_flags;
/**
* Remove path and any files and directories beneath it.
*
* @param path Path to to top level directory to process.
* @param base Root for relative path.
- * @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy
- * of empty directories (will fail if any file is found),
- * GIT_DIRREMOVAL_FILES_AND_DIRS to remove a hierarchy of
- * files and folders,
- * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove empty
- * directories (no failure on file encounter).
- *
+ * @param flags Combination of git_futils_rmdir_flags values
* @return 0 on success; -1 on error.
*/
-extern int git_futils_rmdir_r(const char *path, const char *base, git_directory_removal_type removal_type);
+extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);
/**
* Create and open a temporary file with a `_git2_` suffix.
@@ -266,4 +282,44 @@ extern int git_futils_find_system_file(git_buf *path, const char *filename);
*/
extern int git_futils_fake_symlink(const char *new, const char *old);
+/**
+ * A file stamp represents a snapshot of information about a file that can
+ * be used to test if the file changes. This portable implementation is
+ * based on stat data about that file, but it is possible that OS specific
+ * versions could be implemented in the future.
+ */
+typedef struct {
+ git_time_t mtime;
+ git_off_t size;
+ unsigned int ino;
+} git_futils_filestamp;
+
+/**
+ * Compare stat information for file with reference info.
+ *
+ * This function updates the file stamp to current data for the given path
+ * and returns 0 if the file is up-to-date relative to the prior setting or
+ * 1 if the file has been changed. (This also may return GIT_ENOTFOUND if
+ * the file doesn't exist.)
+ *
+ * @param stamp File stamp to be checked
+ * @param path Path to stat and check if changed
+ * @return 0 if up-to-date, 1 if out-of-date, <0 on error
+ */
+extern int git_futils_filestamp_check(
+ git_futils_filestamp *stamp, const char *path);
+
+/**
+ * Set or reset file stamp data
+ *
+ * This writes the target file stamp. If the source is NULL, this will set
+ * the target stamp to values that will definitely be out of date. If the
+ * source is not NULL, this copies the source values to the target.
+ *
+ * @param tgt File stamp to write to
+ * @param src File stamp to copy from or NULL to clear the target
+ */
+extern void git_futils_filestamp_set(
+ git_futils_filestamp *tgt, const git_futils_filestamp *src);
+
#endif /* INCLUDE_fileops_h__ */
diff --git a/src/filter.c b/src/filter.c
index 28a05235b..f2ab1b85a 100644
--- a/src/filter.c
+++ b/src/filter.c
@@ -33,10 +33,6 @@ void git_text_gather_stats(git_text_stats *stats, const git_buf *text)
else if (c == '\n')
stats->lf++;
- else if (c == 0x85)
- /* Unicode CR+LF */
- stats->crlf++;
-
else if (c == 127)
/* DEL */
stats->nonprintable++;
diff --git a/src/global.c b/src/global.c
index 22127faf7..d085089c3 100644
--- a/src/global.c
+++ b/src/global.c
@@ -6,6 +6,7 @@
*/
#include "common.h"
#include "global.h"
+#include "hash.h"
#include "git2/threads.h"
#include "thread-utils.h"
@@ -38,19 +39,39 @@ git_mutex git__mwindow_mutex;
* functions are not available in that case.
*/
+/*
+ * `git_threads_init()` allows subsystems to perform global setup,
+ * which may take place in the global scope. An explicit memory
+ * fence exists at the exit of `git_threads_init()`. Without this,
+ * CPU cores are free to reorder cache invalidation of `_tls_init`
+ * before cache invalidation of the subsystems' newly written global
+ * state.
+ */
#if defined(GIT_THREADS) && defined(GIT_WIN32)
static DWORD _tls_index;
static int _tls_init = 0;
-void git_threads_init(void)
+int git_threads_init(void)
{
+ int error;
+
if (_tls_init)
- return;
+ return 0;
_tls_index = TlsAlloc();
- _tls_init = 1;
git_mutex_init(&git__mwindow_mutex);
+
+ /* Initialize any other subsystems that have global state */
+ if ((error = git_hash_global_init()) >= 0)
+ _tls_init = 1;
+
+ if (error == 0)
+ _tls_init = 1;
+
+ GIT_MEMORY_BARRIER;
+
+ return error;
}
void git_threads_shutdown(void)
@@ -58,6 +79,9 @@ void git_threads_shutdown(void)
TlsFree(_tls_index);
_tls_init = 0;
git_mutex_free(&git__mwindow_mutex);
+
+ /* Shut down any subsystems that have global state */
+ git_hash_global_shutdown();
}
git_global_st *git__global_state(void)
@@ -88,19 +112,31 @@ static void cb__free_status(void *st)
git__free(st);
}
-void git_threads_init(void)
+int git_threads_init(void)
{
+ int error = 0;
+
if (_tls_init)
- return;
+ return 0;
pthread_key_create(&_tls_key, &cb__free_status);
- _tls_init = 1;
+
+ /* Initialize any other subsystems that have global state */
+ if ((error = git_hash_global_init()) >= 0)
+ _tls_init = 1;
+
+ GIT_MEMORY_BARRIER;
+
+ return error;
}
void git_threads_shutdown(void)
{
pthread_key_delete(_tls_key);
_tls_init = 0;
+
+ /* Shut down any subsystems that have global state */
+ git_hash_global_shutdown();
}
git_global_st *git__global_state(void)
@@ -125,9 +161,10 @@ git_global_st *git__global_state(void)
static git_global_st __state;
-void git_threads_init(void)
+int git_threads_init(void)
{
/* noop */
+ return 0;
}
void git_threads_shutdown(void)
diff --git a/src/global.h b/src/global.h
index 0ad41ee63..b117714a8 100644
--- a/src/global.h
+++ b/src/global.h
@@ -8,6 +8,15 @@
#define INCLUDE_global_h__
#include "mwindow.h"
+#include "hash.h"
+
+#if defined(GIT_THREADS) && defined(_MSC_VER)
+# define GIT_MEMORY_BARRIER MemoryBarrier()
+#elif defined(GIT_THREADS)
+# define GIT_MEMORY_BARRIER __sync_synchronize()
+#else
+# define GIT_MEMORY_BARRIER /* noop */
+#endif
typedef struct {
git_error *last_error;
diff --git a/src/hash.c b/src/hash.c
index 460756913..21db2e129 100644
--- a/src/hash.c
+++ b/src/hash.c
@@ -8,67 +8,40 @@
#include "common.h"
#include "hash.h"
-#if defined(PPC_SHA1)
-# include "ppc/sha1.h"
-#else
-# include "sha1.h"
-#endif
-
-struct git_hash_ctx {
- SHA_CTX c;
-};
-
-git_hash_ctx *git_hash_new_ctx(void)
+int git_hash_buf(git_oid *out, const void *data, size_t len)
{
- git_hash_ctx *ctx = git__malloc(sizeof(*ctx));
-
- if (!ctx)
- return NULL;
-
- SHA1_Init(&ctx->c);
+ git_hash_ctx ctx;
+ int error = 0;
- return ctx;
-}
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
-void git_hash_free_ctx(git_hash_ctx *ctx)
-{
- git__free(ctx);
-}
+ if ((error = git_hash_update(&ctx, data, len)) >= 0)
+ error = git_hash_final(out, &ctx);
-void git_hash_init(git_hash_ctx *ctx)
-{
- assert(ctx);
- SHA1_Init(&ctx->c);
+ git_hash_ctx_cleanup(&ctx);
+
+ return error;
}
-void git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n)
{
- assert(ctx);
- SHA1_Update(&ctx->c, data, len);
-}
+ git_hash_ctx ctx;
+ size_t i;
+ int error = 0;
-void git_hash_final(git_oid *out, git_hash_ctx *ctx)
-{
- assert(ctx);
- SHA1_Final(out->id, &ctx->c);
-}
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
-void git_hash_buf(git_oid *out, const void *data, size_t len)
-{
- SHA_CTX c;
+ for (i = 0; i < n; i++) {
+ if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0)
+ goto done;
+ }
- SHA1_Init(&c);
- SHA1_Update(&c, data, len);
- SHA1_Final(out->id, &c);
-}
+ error = git_hash_final(out, &ctx);
-void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n)
-{
- SHA_CTX c;
- size_t i;
+done:
+ git_hash_ctx_cleanup(&ctx);
- SHA1_Init(&c);
- for (i = 0; i < n; i++)
- SHA1_Update(&c, vec[i].data, vec[i].len);
- SHA1_Final(out->id, &c);
+ return error;
}
diff --git a/src/hash.h b/src/hash.h
index 33d7b20cd..127be282f 100644
--- a/src/hash.h
+++ b/src/hash.h
@@ -9,21 +9,35 @@
#include "git2/oid.h"
+typedef struct git_hash_prov git_hash_prov;
typedef struct git_hash_ctx git_hash_ctx;
+int git_hash_global_init(void);
+void git_hash_global_shutdown(void);
+
+int git_hash_ctx_init(git_hash_ctx *ctx);
+void git_hash_ctx_cleanup(git_hash_ctx *ctx);
+
+#if defined(OPENSSL_SHA1)
+# include "hash/hash_openssl.h"
+#elif defined(WIN32_SHA1)
+# include "hash/hash_win32.h"
+#elif defined(PPC_SHA1)
+# include "hash/hash_ppc.h"
+#else
+# include "hash/hash_generic.h"
+#endif
+
typedef struct {
void *data;
size_t len;
} git_buf_vec;
-git_hash_ctx *git_hash_new_ctx(void);
-void git_hash_free_ctx(git_hash_ctx *ctx);
-
-void git_hash_init(git_hash_ctx *c);
-void git_hash_update(git_hash_ctx *c, const void *data, size_t len);
-void git_hash_final(git_oid *out, git_hash_ctx *c);
+int git_hash_init(git_hash_ctx *c);
+int git_hash_update(git_hash_ctx *c, const void *data, size_t len);
+int git_hash_final(git_oid *out, git_hash_ctx *c);
-void git_hash_buf(git_oid *out, const void *data, size_t len);
-void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);
+int git_hash_buf(git_oid *out, const void *data, size_t len);
+int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);
#endif /* INCLUDE_hash_h__ */
diff --git a/src/sha1/sha1.c b/src/hash/hash_generic.c
index 8aaedeb8f..30d7a5d1e 100644
--- a/src/sha1/sha1.c
+++ b/src/hash/hash_generic.c
@@ -6,7 +6,8 @@
*/
#include "common.h"
-#include "sha1.h"
+#include "hash.h"
+#include "hash/hash_generic.h"
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
@@ -112,7 +113,7 @@
#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E )
#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E )
-static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data)
+static void hash__block(git_hash_ctx *ctx, const unsigned int *data)
{
unsigned int A,B,C,D,E;
unsigned int array[16];
@@ -220,7 +221,7 @@ static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data)
ctx->H[4] += E;
}
-void git__blk_SHA1_Init(blk_SHA_CTX *ctx)
+int git_hash_init(git_hash_ctx *ctx)
{
ctx->size = 0;
@@ -230,9 +231,11 @@ void git__blk_SHA1_Init(blk_SHA_CTX *ctx)
ctx->H[2] = 0x98badcfe;
ctx->H[3] = 0x10325476;
ctx->H[4] = 0xc3d2e1f0;
+
+ return 0;
}
-void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len)
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
{
unsigned int lenW = ctx->size & 63;
@@ -248,19 +251,21 @@ void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len)
len -= left;
data = ((const char *)data + left);
if (lenW)
- return;
- blk_SHA1_Block(ctx, ctx->W);
+ return 0;
+ hash__block(ctx, ctx->W);
}
while (len >= 64) {
- blk_SHA1_Block(ctx, data);
+ hash__block(ctx, data);
data = ((const char *)data + 64);
len -= 64;
}
if (len)
memcpy(ctx->W, data, len);
+
+ return 0;
}
-void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx)
+int git_hash_final(git_oid *out, git_hash_ctx *ctx)
{
static const unsigned char pad[64] = { 0x80 };
unsigned int padlen[2];
@@ -271,10 +276,13 @@ void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx)
padlen[1] = htonl((uint32_t)(ctx->size << 3));
i = ctx->size & 63;
- git__blk_SHA1_Update(ctx, pad, 1+ (63 & (55 - i)));
- git__blk_SHA1_Update(ctx, padlen, 8);
+ git_hash_update(ctx, pad, 1+ (63 & (55 - i)));
+ git_hash_update(ctx, padlen, 8);
/* Output hash */
for (i = 0; i < 5; i++)
- put_be32(hashout + i*4, ctx->H[i]);
+ put_be32(out->id + i*4, ctx->H[i]);
+
+ return 0;
}
+
diff --git a/src/hash/hash_generic.h b/src/hash/hash_generic.h
new file mode 100644
index 000000000..7c4be7873
--- /dev/null
+++ b/src/hash/hash_generic.h
@@ -0,0 +1,24 @@
+/*
+ * 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_hash_generic_h__
+#define INCLUDE_hash_generic_h__
+
+#include "hash.h"
+
+struct git_hash_ctx {
+ unsigned long long size;
+ unsigned int H[5];
+ unsigned int W[16];
+};
+
+#define git_hash_global_init() 0
+#define git_hash_global_shutdown() /* noop */
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx)
+
+#endif /* INCLUDE_hash_generic_h__ */
diff --git a/src/hash/hash_openssl.h b/src/hash/hash_openssl.h
new file mode 100644
index 000000000..3ae49a732
--- /dev/null
+++ b/src/hash/hash_openssl.h
@@ -0,0 +1,45 @@
+/*
+ * 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_hash_openssl_h__
+#define INCLUDE_hash_openssl_h__
+
+#include "hash.h"
+
+#include <openssl/sha.h>
+
+struct git_hash_ctx {
+ SHA_CTX c;
+};
+
+#define git_hash_global_init() 0
+#define git_hash_global_shutdown() /* noop */
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx)
+
+GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx)
+{
+ assert(ctx);
+ SHA1_Init(&ctx->c);
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx);
+ SHA1_Update(&ctx->c, data, len);
+ return 0;
+}
+
+GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx);
+ SHA1_Final(out->id, &ctx->c);
+ return 0;
+}
+
+#endif /* INCLUDE_hash_openssl_h__ */
diff --git a/src/ppc/sha1.c b/src/hash/hash_ppc.c
index 803b81d0a..de89e9f5e 100644
--- a/src/ppc/sha1.c
+++ b/src/hash/hash_ppc.c
@@ -4,14 +4,17 @@
* 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 <stdio.h>
#include <string.h>
-#include "sha1.h"
-extern void ppc_sha1_core(uint32_t *hash, const unsigned char *p,
+#include "common.h"
+#include "hash.h"
+
+extern void hash_ppc_core(uint32_t *hash, const unsigned char *p,
unsigned int nblocks);
-int ppc_SHA1_Init(ppc_SHA_CTX *c)
+int git_hash_init(git_hash_ctx *c)
{
c->hash[0] = 0x67452301;
c->hash[1] = 0xEFCDAB89;
@@ -23,7 +26,7 @@ int ppc_SHA1_Init(ppc_SHA_CTX *c)
return 0;
}
-int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n)
+int git_hash_update(git_hash_ctx *c, const void *ptr, size_t n)
{
unsigned long nb;
const unsigned char *p = ptr;
@@ -36,12 +39,12 @@ int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n)
nb = n;
memcpy(&c->buf.b[c->cnt], p, nb);
if ((c->cnt += nb) == 64) {
- ppc_sha1_core(c->hash, c->buf.b, 1);
+ hash_ppc_core(c->hash, c->buf.b, 1);
c->cnt = 0;
}
} else {
nb = n >> 6;
- ppc_sha1_core(c->hash, p, nb);
+ hash_ppc_core(c->hash, p, nb);
nb <<= 6;
}
n -= nb;
@@ -50,7 +53,7 @@ int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n)
return 0;
}
-int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c)
+int git_hash_final(git_oid *oid, git_hash_ctx *c)
{
unsigned int cnt = c->cnt;
@@ -58,13 +61,14 @@ int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c)
if (cnt > 56) {
if (cnt < 64)
memset(&c->buf.b[cnt], 0, 64 - cnt);
- ppc_sha1_core(c->hash, c->buf.b, 1);
+ hash_ppc_core(c->hash, c->buf.b, 1);
cnt = 0;
}
if (cnt < 56)
memset(&c->buf.b[cnt], 0, 56 - cnt);
c->buf.l[7] = c->len;
- ppc_sha1_core(c->hash, c->buf.b, 1);
- memcpy(hash, c->hash, 20);
+ hash_ppc_core(c->hash, c->buf.b, 1);
+ memcpy(oid->id, c->hash, 20);
return 0;
}
+
diff --git a/src/ppc/sha1.h b/src/hash/hash_ppc.h
index aca4e5dda..935f73f7f 100644
--- a/src/ppc/sha1.h
+++ b/src/hash/hash_ppc.h
@@ -4,9 +4,13 @@
* 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_hash_ppc_h__
+#define INCLUDE_hash_ppc_h__
+
#include <stdint.h>
-typedef struct {
+struct git_hash_ctx {
uint32_t hash[5];
uint32_t cnt;
uint64_t len;
@@ -14,13 +18,11 @@ typedef struct {
unsigned char b[64];
uint64_t l[8];
} buf;
-} ppc_SHA_CTX;
+};
-int ppc_SHA1_Init(ppc_SHA_CTX *c);
-int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *p, unsigned long n);
-int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c);
+#define git_hash_global_init() 0
+#define git_hash_global_shutdown() /* noop */
+#define git_hash_ctx_init(ctx) git_hash_init(ctx)
+#define git_hash_ctx_cleanup(ctx)
-#define SHA_CTX ppc_SHA_CTX
-#define SHA1_Init ppc_SHA1_Init
-#define SHA1_Update ppc_SHA1_Update
-#define SHA1_Final ppc_SHA1_Final
+#endif /* INCLUDE_hash_generic_h__ */
diff --git a/src/ppc/sha1ppc.S b/src/hash/hash_ppc_core.S
index 1711eef6e..1de816cf5 100644
--- a/src/ppc/sha1ppc.S
+++ b/src/hash/hash_ppc_core.S
@@ -162,8 +162,8 @@ add RE(t),RE(t),%r0; rotlwi RB(t),RB(t),30
STEPUP4(fn, (t)+12, (s)+12,); \
STEPUP4(fn, (t)+16, (s)+16, loadk)
- .globl ppc_sha1_core
-ppc_sha1_core:
+ .globl hash_ppc_core
+hash_ppc_core:
stwu %r1,-80(%r1)
stmw %r13,4(%r1)
diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c
new file mode 100644
index 000000000..a89dffa7c
--- /dev/null
+++ b/src/hash/hash_win32.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "global.h"
+#include "hash.h"
+#include "hash/hash_win32.h"
+
+#include <wincrypt.h>
+#include <strsafe.h>
+
+static struct git_hash_prov hash_prov = {0};
+
+/* Hash initialization */
+
+/* Initialize CNG, if available */
+GIT_INLINE(int) hash_cng_prov_init(void)
+{
+ OSVERSIONINFOEX version_test = {0};
+ DWORD version_test_mask;
+ DWORDLONG version_condition_mask = 0;
+ char dll_path[MAX_PATH];
+ DWORD dll_path_len, size_len;
+
+ return -1;
+
+ /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */
+ version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
+ version_test.dwMajorVersion = 6;
+ version_test.dwMinorVersion = 0;
+ version_test.wServicePackMajor = 1;
+ version_test.wServicePackMinor = 0;
+
+ version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR);
+
+ VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
+ VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
+
+ if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask))
+ return -1;
+
+ /* Load bcrypt.dll explicitly from the system directory */
+ if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH ||
+ StringCchCat(dll_path, MAX_PATH, "\\") < 0 ||
+ StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 ||
+ (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL)
+ return -1;
+
+ /* Load the function addresses */
+ if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL ||
+ (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL ||
+ (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL ||
+ (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL ||
+ (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL ||
+ (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL ||
+ (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) {
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ /* Load the SHA1 algorithm */
+ if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) {
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ /* Get storage space for the hash object */
+ if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) {
+ hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);
+ FreeLibrary(hash_prov.prov.cng.dll);
+ return -1;
+ }
+
+ hash_prov.type = CNG;
+ return 0;
+}
+
+GIT_INLINE(void) hash_cng_prov_shutdown(void)
+{
+ hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0);
+ FreeLibrary(hash_prov.prov.cng.dll);
+
+ hash_prov.type = INVALID;
+}
+
+/* Initialize CryptoAPI */
+GIT_INLINE(int) hash_cryptoapi_prov_init()
+{
+ if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
+ return -1;
+
+ hash_prov.type = CRYPTOAPI;
+ return 0;
+}
+
+GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void)
+{
+ CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0);
+
+ hash_prov.type = INVALID;
+}
+
+int git_hash_global_init()
+{
+ int error = 0;
+
+ if (hash_prov.type != INVALID)
+ return 0;
+
+ if ((error = hash_cng_prov_init()) < 0)
+ error = hash_cryptoapi_prov_init();
+
+ return error;
+}
+
+void git_hash_global_shutdown()
+{
+ if (hash_prov.type == CNG)
+ hash_cng_prov_shutdown();
+ else if(hash_prov.type == CRYPTOAPI)
+ hash_cryptoapi_prov_shutdown();
+}
+
+/* CryptoAPI: available in Windows XP and newer */
+
+GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_ctx *ctx)
+{
+ ctx->type = CRYPTOAPI;
+ ctx->prov = &hash_prov;
+
+ return git_hash_init(ctx);
+}
+
+GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+
+ if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) {
+ ctx->ctx.cryptoapi.valid = 0;
+ return -1;
+ }
+
+ ctx->ctx.cryptoapi.valid = 1;
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, (const BYTE *)data, len, 0))
+ return -1;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx)
+{
+ DWORD len = 20;
+ int error = 0;
+
+ assert(ctx->ctx.cryptoapi.valid);
+
+ if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0))
+ error = -1;
+
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+ ctx->ctx.cryptoapi.valid = 0;
+
+ return error;
+}
+
+GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx)
+{
+ if (ctx->ctx.cryptoapi.valid)
+ CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle);
+}
+
+/* CNG: Available in Windows Server 2008 and newer */
+
+GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx)
+{
+ if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL)
+ return -1;
+
+ if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) {
+ git__free(ctx->ctx.cng.hash_object);
+ return -1;
+ }
+
+ ctx->type = CNG;
+ ctx->prov = &hash_prov;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx)
+{
+ BYTE hash[GIT_OID_RAWSZ];
+
+ if (!ctx->ctx.cng.updated)
+ return 0;
+
+ /* CNG needs to be finished to restart */
+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0)
+ return -1;
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, (PBYTE)data, len, 0) < 0)
+ return -1;
+
+ return 0;
+}
+
+GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx)
+{
+ if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0)
+ return -1;
+
+ ctx->ctx.cng.updated = 0;
+
+ return 0;
+}
+
+GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx)
+{
+ ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle);
+ git__free(ctx->ctx.cng.hash_object);
+}
+
+/* Indirection between CryptoAPI and CNG */
+
+int git_hash_ctx_init(git_hash_ctx *ctx)
+{
+ int error = 0;
+
+ assert(ctx);
+
+ /*
+ * When compiled with GIT_THREADS, the global hash_prov data is
+ * initialized with git_threads_init. Otherwise, it must be initialized
+ * at first use.
+ */
+ if (hash_prov.type == INVALID && (error = git_hash_global_init()) < 0)
+ return error;
+
+ memset(ctx, 0x0, sizeof(git_hash_ctx));
+
+ return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx);
+}
+
+int git_hash_init(git_hash_ctx *ctx)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx);
+}
+
+int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len);
+}
+
+int git_hash_final(git_oid *out, git_hash_ctx *ctx)
+{
+ assert(ctx && ctx->type);
+ return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx);
+}
+
+void git_hash_ctx_cleanup(git_hash_ctx *ctx)
+{
+ assert(ctx);
+
+ if (ctx->type == CNG)
+ hash_ctx_cng_cleanup(ctx);
+ else if(ctx->type == CRYPTOAPI)
+ hash_ctx_cryptoapi_cleanup(ctx);
+}
diff --git a/src/hash/hash_win32.h b/src/hash/hash_win32.h
new file mode 100644
index 000000000..b91da3e37
--- /dev/null
+++ b/src/hash/hash_win32.h
@@ -0,0 +1,140 @@
+/*
+ * 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_hash_win32_h__
+#define INCLUDE_hash_win32_h__
+
+#include "common.h"
+#include "hash.h"
+
+#include <wincrypt.h>
+#include <strsafe.h>
+
+enum hash_win32_prov_type {
+ INVALID = 0,
+ CRYPTOAPI,
+ CNG
+};
+
+/*
+ * CryptoAPI is available for hashing on Windows XP and newer.
+ */
+
+struct hash_cryptoapi_prov {
+ HCRYPTPROV handle;
+};
+
+/*
+ * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is
+ * preferred, however it is only available on Windows 2008 and newer and
+ * must therefore be dynamically loaded, and we must inline constants that
+ * would not exist when building in pre-Windows 2008 environments.
+ */
+
+#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll"
+
+/* BCRYPT_SHA1_ALGORITHM */
+#define GIT_HASH_CNG_HASH_TYPE L"SHA1"
+
+/* BCRYPT_OBJECT_LENGTH */
+#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength"
+
+/* BCRYPT_HASH_REUSEABLE_FLAGS */
+#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020
+
+/* Function declarations for CNG */
+typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm,
+ LPCWSTR pszAlgId,
+ LPCWSTR pszImplementation,
+ DWORD dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)(
+ HANDLE /* BCRYPT_HANDLE */ hObject,
+ LPCWSTR pszProperty,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG *pcbResult,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
+ HANDLE /* BCRYPT_HASH_HANDLE */ *phHash,
+ PUCHAR pbHashObject, ULONG cbHashObject,
+ PUCHAR pbSecret,
+ ULONG cbSecret,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
+ PUCHAR pbOutput,
+ ULONG cbOutput,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash,
+ PUCHAR pbInput,
+ ULONG cbInput,
+ ULONG dwFlags);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)(
+ HANDLE /* BCRYPT_HASH_HANDLE */ hHash);
+
+typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)(
+ HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm,
+ ULONG dwFlags);
+
+struct hash_cng_prov {
+ /* DLL for CNG */
+ HINSTANCE dll;
+
+ /* Function pointers for CNG */
+ hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider;
+ hash_win32_cng_get_property_fn get_property;
+ hash_win32_cng_create_hash_fn create_hash;
+ hash_win32_cng_finish_hash_fn finish_hash;
+ hash_win32_cng_hash_data_fn hash_data;
+ hash_win32_cng_destroy_hash_fn destroy_hash;
+ hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider;
+
+ HANDLE /* BCRYPT_ALG_HANDLE */ handle;
+ DWORD hash_object_size;
+};
+
+struct git_hash_prov {
+ enum hash_win32_prov_type type;
+
+ union {
+ struct hash_cryptoapi_prov cryptoapi;
+ struct hash_cng_prov cng;
+ } prov;
+};
+
+/* Hash contexts */
+
+struct hash_cryptoapi_ctx {
+ bool valid;
+ HCRYPTHASH hash_handle;
+};
+
+struct hash_cng_ctx {
+ bool updated;
+ HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle;
+ PBYTE hash_object;
+};
+
+struct git_hash_ctx {
+ enum hash_win32_prov_type type;
+ git_hash_prov *prov;
+
+ union {
+ struct hash_cryptoapi_ctx cryptoapi;
+ struct hash_cng_ctx cng;
+ } ctx;
+};
+
+#endif /* INCLUDE_hash_openssl_h__ */
diff --git a/src/index.c b/src/index.c
index f9f3b14cc..128dd18cf 100644
--- a/src/index.c
+++ b/src/index.c
@@ -13,6 +13,8 @@
#include "tree.h"
#include "tree-cache.h"
#include "hash.h"
+#include "iterator.h"
+#include "pathspec.h"
#include "git2/odb.h"
#include "git2/oid.h"
#include "git2/blob.h"
@@ -81,6 +83,11 @@ struct entry_long {
char path[1]; /* arbitrary length */
};
+struct entry_srch_key {
+ const char *path;
+ int stage;
+};
+
/* local declarations */
static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size);
static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size);
@@ -90,53 +97,126 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
static int is_index_extended(git_index *index);
static int write_index(git_index *index, git_filebuf *file);
+static int index_find(git_index *index, const char *path, int stage);
+
static void index_entry_free(git_index_entry *entry);
+static void index_entry_reuc_free(git_index_reuc_entry *reuc);
+
+GIT_INLINE(int) index_entry_stage(const git_index_entry *entry)
+{
+ return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;
+}
static int index_srch(const void *key, const void *array_member)
{
+ const struct entry_srch_key *srch_key = key;
const git_index_entry *entry = array_member;
+ int ret;
+
+ ret = strcmp(srch_key->path, entry->path);
- return strcmp(key, entry->path);
+ if (ret == 0)
+ ret = srch_key->stage - index_entry_stage(entry);
+
+ return ret;
}
static int index_isrch(const void *key, const void *array_member)
{
+ const struct entry_srch_key *srch_key = key;
const git_index_entry *entry = array_member;
+ int ret;
- return strcasecmp(key, entry->path);
+ ret = strcasecmp(srch_key->path, entry->path);
+
+ if (ret == 0)
+ ret = srch_key->stage - index_entry_stage(entry);
+
+ return ret;
+}
+
+static int index_cmp_path(const void *a, const void *b)
+{
+ return strcmp((const char *)a, (const char *)b);
+}
+
+static int index_icmp_path(const void *a, const void *b)
+{
+ return strcasecmp((const char *)a, (const char *)b);
+}
+
+static int index_srch_path(const void *path, const void *array_member)
+{
+ const git_index_entry *entry = array_member;
+
+ return strcmp((const char *)path, entry->path);
+}
+
+static int index_isrch_path(const void *path, const void *array_member)
+{
+ const git_index_entry *entry = array_member;
+
+ return strcasecmp((const char *)path, entry->path);
}
static int index_cmp(const void *a, const void *b)
{
+ int diff;
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
- return strcmp(entry_a->path, entry_b->path);
+ diff = strcmp(entry_a->path, entry_b->path);
+
+ if (diff == 0)
+ diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b));
+
+ return diff;
}
static int index_icmp(const void *a, const void *b)
{
+ int diff;
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
- return strcasecmp(entry_a->path, entry_b->path);
+ diff = strcasecmp(entry_a->path, entry_b->path);
+
+ if (diff == 0)
+ diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b));
+
+ return diff;
+}
+
+static int reuc_srch(const void *key, const void *array_member)
+{
+ const git_index_reuc_entry *reuc = array_member;
+
+ return strcmp(key, reuc->path);
}
-static int unmerged_srch(const void *key, const void *array_member)
+static int reuc_isrch(const void *key, const void *array_member)
{
- const git_index_entry_unmerged *entry = array_member;
+ const git_index_reuc_entry *reuc = array_member;
- return strcmp(key, entry->path);
+ return strcasecmp(key, reuc->path);
}
-static int unmerged_cmp(const void *a, const void *b)
+static int reuc_cmp(const void *a, const void *b)
{
- const git_index_entry_unmerged *info_a = a;
- const git_index_entry_unmerged *info_b = b;
+ const git_index_reuc_entry *info_a = a;
+ const git_index_reuc_entry *info_b = b;
return strcmp(info_a->path, info_b->path);
}
+static int reuc_icmp(const void *a, const void *b)
+{
+ const git_index_reuc_entry *info_a = a;
+ const git_index_reuc_entry *info_b = b;
+
+ return strcasecmp(info_a->path, info_b->path);
+}
+
static unsigned int index_create_mode(unsigned int mode)
{
if (S_ISLNK(mode))
@@ -165,40 +245,59 @@ static unsigned int index_merge_mode(
static void index_set_ignore_case(git_index *index, bool ignore_case)
{
index->entries._cmp = ignore_case ? index_icmp : index_cmp;
+ index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path;
index->entries_search = ignore_case ? index_isrch : index_srch;
+ index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path;
index->entries.sorted = 0;
git_vector_sort(&index->entries);
+
+ index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp;
+ index->reuc_search = ignore_case ? reuc_isrch : reuc_srch;
+ index->reuc.sorted = 0;
+ git_vector_sort(&index->reuc);
}
int git_index_open(git_index **index_out, const char *index_path)
{
git_index *index;
- assert(index_out && index_path);
+ assert(index_out);
index = git__calloc(1, sizeof(git_index));
GITERR_CHECK_ALLOC(index);
- index->index_file_path = git__strdup(index_path);
- GITERR_CHECK_ALLOC(index->index_file_path);
+ if (index_path != NULL) {
+ index->index_file_path = git__strdup(index_path);
+ GITERR_CHECK_ALLOC(index->index_file_path);
+
+ /* Check if index file is stored on disk already */
+ if (git_path_exists(index->index_file_path) == true)
+ index->on_disk = 1;
+ }
if (git_vector_init(&index->entries, 32, index_cmp) < 0)
return -1;
+ index->entries_cmp_path = index_cmp_path;
index->entries_search = index_srch;
-
- /* Check if index file is stored on disk already */
- if (git_path_exists(index->index_file_path) == true)
- index->on_disk = 1;
+ index->entries_search_path = index_srch_path;
+ index->reuc_search = reuc_srch;
*index_out = index;
GIT_REFCOUNT_INC(index);
- return git_index_read(index);
+
+ return (index_path != NULL) ? git_index_read(index) : 0;
+}
+
+int git_index_new(git_index **out)
+{
+ return git_index_open(out, NULL);
}
static void index_free(git_index *index)
{
git_index_entry *e;
+ git_index_reuc_entry *reuc;
unsigned int i;
git_index_clear(index);
@@ -206,10 +305,10 @@ static void index_free(git_index *index)
index_entry_free(e);
}
git_vector_free(&index->entries);
- git_vector_foreach(&index->unmerged, i, e) {
- index_entry_free(e);
+ git_vector_foreach(&index->reuc, i, reuc) {
+ index_entry_reuc_free(reuc);
}
- git_vector_free(&index->unmerged);
+ git_vector_free(&index->reuc);
git__free(index->index_file_path);
git__free(index);
@@ -236,21 +335,27 @@ void git_index_clear(git_index *index)
git__free(e);
}
- for (i = 0; i < index->unmerged.length; ++i) {
- git_index_entry_unmerged *e;
- e = git_vector_get(&index->unmerged, i);
+ for (i = 0; i < index->reuc.length; ++i) {
+ git_index_reuc_entry *e;
+ e = git_vector_get(&index->reuc, i);
git__free(e->path);
git__free(e);
}
git_vector_clear(&index->entries);
- git_vector_clear(&index->unmerged);
- index->last_modified = 0;
+ git_vector_clear(&index->reuc);
+ git_futils_filestamp_set(&index->stamp, NULL);
git_tree_cache_free(index->tree);
index->tree = NULL;
}
+static int create_index_error(int error, const char *msg)
+{
+ giterr_set(GITERR_INDEX, msg);
+ return error;
+}
+
int git_index_set_caps(git_index *index, unsigned int caps)
{
int old_ignore_case;
@@ -265,11 +370,8 @@ int git_index_set_caps(git_index *index, unsigned int caps)
if (INDEX_OWNER(index) == NULL ||
git_repository_config__weakptr(&cfg, INDEX_OWNER(index)) < 0)
- {
- giterr_set(GITERR_INDEX,
- "Cannot get repository config to set index caps");
- return -1;
- }
+ return create_index_error(-1,
+ "Cannot get repository config to set index caps");
if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0)
index->ignore_case = (val != 0);
@@ -301,11 +403,13 @@ unsigned int git_index_caps(const git_index *index)
int git_index_read(git_index *index)
{
- int error, updated;
+ int error = 0, updated;
git_buf buffer = GIT_BUF_INIT;
- time_t mtime;
+ git_futils_filestamp stamp = {0};
- assert(index->index_file_path);
+ if (!index->index_file_path)
+ return create_index_error(-1,
+ "Failed to read index: The index is in-memory only");
if (!index->on_disk || git_path_exists(index->index_file_path) == false) {
git_index_clear(index);
@@ -313,33 +417,35 @@ int git_index_read(git_index *index)
return 0;
}
- /* We don't want to update the mtime if we fail to parse the index */
- mtime = index->last_modified;
- error = git_futils_readbuffer_updated(
- &buffer, index->index_file_path, &mtime, &updated);
+ updated = git_futils_filestamp_check(&stamp, index->index_file_path);
+ if (updated <= 0)
+ return updated;
+
+ error = git_futils_readbuffer(&buffer, index->index_file_path);
if (error < 0)
return error;
- if (updated) {
- git_index_clear(index);
- error = parse_index(index, buffer.ptr, buffer.size);
-
- if (!error)
- index->last_modified = mtime;
+ git_index_clear(index);
+ error = parse_index(index, buffer.ptr, buffer.size);
- git_buf_free(&buffer);
- }
+ if (!error)
+ git_futils_filestamp_set(&index->stamp, &stamp);
+ git_buf_free(&buffer);
return error;
}
int git_index_write(git_index *index)
{
git_filebuf file = GIT_FILEBUF_INIT;
- struct stat indexst;
int error;
+ if (!index->index_file_path)
+ return create_index_error(-1,
+ "Failed to read index: The index is in-memory only");
+
git_vector_sort(&index->entries);
+ git_vector_sort(&index->reuc);
if ((error = git_filebuf_open(
&file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0)
@@ -353,33 +459,63 @@ int git_index_write(git_index *index)
if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0)
return error;
- if (p_stat(index->index_file_path, &indexst) == 0) {
- index->last_modified = indexst.st_mtime;
- index->on_disk = 1;
- }
+ error = git_futils_filestamp_check(&index->stamp, index->index_file_path);
+ if (error < 0)
+ return error;
+ index->on_disk = 1;
return 0;
}
+int git_index_write_tree(git_oid *oid, git_index *index)
+{
+ git_repository *repo;
+
+ assert(oid && index);
+
+ repo = INDEX_OWNER(index);
+
+ if (repo == NULL)
+ return create_index_error(-1, "Failed to write tree. "
+ "The index file is not backed up by an existing repository");
+
+ return git_tree__write_index(oid, index, repo);
+}
+
+int git_index_write_tree_to(git_oid *oid, git_index *index, git_repository *repo)
+{
+ assert(oid && index && repo);
+ return git_tree__write_index(oid, index, repo);
+}
+
unsigned int git_index_entrycount(git_index *index)
{
assert(index);
return (unsigned int)index->entries.length;
}
-unsigned int git_index_entrycount_unmerged(git_index *index)
+git_index_entry *git_index_get_byindex(git_index *index, size_t n)
{
assert(index);
- return (unsigned int)index->unmerged.length;
+ git_vector_sort(&index->entries);
+ return git_vector_get(&index->entries, n);
}
-git_index_entry *git_index_get(git_index *index, size_t n)
+git_index_entry *git_index_get_bypath(git_index *index, const char *path, int stage)
{
+ int pos;
+
+ assert(index);
+
git_vector_sort(&index->entries);
- return git_vector_get(&index->entries, n);
+
+ if((pos = index_find(index, path, stage)) < 0)
+ return NULL;
+
+ return git_index_get_byindex(index, pos);
}
-void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry)
+void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st)
{
entry->ctime.seconds = (git_time_t)st->st_ctime;
entry->mtime.seconds = (git_time_t)st->st_mtime;
@@ -393,7 +529,23 @@ void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry)
entry->file_size = st->st_size;
}
-static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage)
+int git_index_entry__cmp(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcmp(entry_a->path, entry_b->path);
+}
+
+int git_index_entry__cmp_icase(const void *a, const void *b)
+{
+ const git_index_entry *entry_a = a;
+ const git_index_entry *entry_b = b;
+
+ return strcasecmp(entry_a->path, entry_b->path);
+}
+
+static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path)
{
git_index_entry *entry = NULL;
struct stat st;
@@ -402,15 +554,16 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const
git_buf full_path = GIT_BUF_INIT;
int error;
- assert(stage >= 0 && stage <= 3);
+ if (INDEX_OWNER(index) == NULL)
+ return create_index_error(-1,
+ "Could not initialize index entry. "
+ "Index is not backed up by an existing repository.");
- if (INDEX_OWNER(index) == NULL ||
- (workdir = git_repository_workdir(INDEX_OWNER(index))) == NULL)
- {
- giterr_set(GITERR_INDEX,
+ workdir = git_repository_workdir(INDEX_OWNER(index));
+
+ if (!workdir)
+ return create_index_error(GIT_EBAREREPO,
"Could not initialize index entry. Repository is bare");
- return -1;
- }
if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0)
return error;
@@ -433,10 +586,9 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const
entry = git__calloc(1, sizeof(git_index_entry));
GITERR_CHECK_ALLOC(entry);
- git_index__init_entry_from_stat(&st, entry);
+ git_index_entry__init_from_stat(entry, &st);
entry->oid = oid;
- entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT);
entry->path = git__strdup(rel_path);
GITERR_CHECK_ALLOC(entry->path);
@@ -444,6 +596,46 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const
return 0;
}
+static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
+ const char *path,
+ int ancestor_mode, git_oid *ancestor_oid,
+ int our_mode, git_oid *our_oid, int their_mode, git_oid *their_oid)
+{
+ git_index_reuc_entry *reuc = NULL;
+
+ assert(reuc_out && path);
+
+ *reuc_out = NULL;
+
+ reuc = git__calloc(1, sizeof(git_index_reuc_entry));
+ GITERR_CHECK_ALLOC(reuc);
+
+ reuc->path = git__strdup(path);
+ if (reuc->path == NULL)
+ return -1;
+
+ reuc->mode[0] = ancestor_mode;
+ git_oid_cpy(&reuc->oid[0], ancestor_oid);
+
+ reuc->mode[1] = our_mode;
+ git_oid_cpy(&reuc->oid[1], our_oid);
+
+ reuc->mode[2] = their_mode;
+ git_oid_cpy(&reuc->oid[2], their_oid);
+
+ *reuc_out = reuc;
+ return 0;
+}
+
+static void index_entry_reuc_free(git_index_reuc_entry *reuc)
+{
+ if (!reuc)
+ return;
+
+ git__free(reuc->path);
+ git__free(reuc);
+}
+
static git_index_entry *index_entry_dup(const git_index_entry *source_entry)
{
git_index_entry *entry;
@@ -486,10 +678,10 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
if (path_length < GIT_IDXENTRY_NAMEMASK)
entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK;
else
- entry->flags |= GIT_IDXENTRY_NAMEMASK;;
+ entry->flags |= GIT_IDXENTRY_NAMEMASK;
/* look if an entry with this path already exists */
- if ((position = git_index_find(index, entry->path)) >= 0) {
+ if ((position = index_find(index, entry->path, index_entry_stage(entry))) >= 0) {
existing = (git_index_entry **)&index->entries.contents[position];
/* update filemode to existing values if stat is not trusted */
@@ -510,34 +702,56 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace)
return 0;
}
-static int index_add(git_index *index, const char *path, int stage, int replace)
+static int index_conflict_to_reuc(git_index *index, const char *path)
{
- git_index_entry *entry = NULL;
+ git_index_entry *conflict_entries[3];
+ int ancestor_mode, our_mode, their_mode;
+ git_oid *ancestor_oid, *our_oid, *their_oid;
int ret;
- if ((ret = index_entry_init(&entry, index, path, stage)) < 0 ||
- (ret = index_insert(index, entry, replace)) < 0)
- {
- index_entry_free(entry);
+ if ((ret = git_index_conflict_get(&conflict_entries[0],
+ &conflict_entries[1], &conflict_entries[2], index, path)) < 0)
return ret;
- }
- git_tree_cache_invalidate_path(index->tree, entry->path);
- return 0;
-}
+ ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode;
+ our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode;
+ their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode;
-int git_index_add(git_index *index, const char *path, int stage)
-{
- return index_add(index, path, stage, 1);
+ ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->oid;
+ our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->oid;
+ their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->oid;
+
+ if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid,
+ our_mode, our_oid, their_mode, their_oid)) >= 0)
+ ret = git_index_conflict_remove(index, path);
+
+ return ret;
}
-int git_index_append(git_index *index, const char *path, int stage)
+int git_index_add_from_workdir(git_index *index, const char *path)
{
- return index_add(index, path, stage, 0);
+ git_index_entry *entry = NULL;
+ int ret;
+
+ assert(index && path);
+
+ if ((ret = index_entry_init(&entry, index, path)) < 0 ||
+ (ret = index_insert(index, entry, 1)) < 0)
+ goto on_error;
+
+ /* Adding implies conflict was resolved, move conflict entries to REUC */
+ if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND)
+ goto on_error;
+
+ git_tree_cache_invalidate_path(index->tree, entry->path);
+ return 0;
+
+on_error:
+ index_entry_free(entry);
+ return ret;
}
-static int index_add2(
- git_index *index, const git_index_entry *source_entry, int replace)
+int git_index_add(git_index *index, const git_index_entry *source_entry)
{
git_index_entry *entry = NULL;
int ret;
@@ -546,7 +760,7 @@ static int index_add2(
if (entry == NULL)
return -1;
- if ((ret = index_insert(index, entry, replace)) < 0) {
+ if ((ret = index_insert(index, entry, 1)) < 0) {
index_entry_free(entry);
return ret;
}
@@ -555,23 +769,17 @@ static int index_add2(
return 0;
}
-int git_index_add2(git_index *index, const git_index_entry *source_entry)
-{
- return index_add2(index, source_entry, 1);
-}
-
-int git_index_append2(git_index *index, const git_index_entry *source_entry)
-{
- return index_add2(index, source_entry, 0);
-}
-
-int git_index_remove(git_index *index, int position)
+int git_index_remove(git_index *index, const char *path, int stage)
{
+ int position;
int error;
git_index_entry *entry;
git_vector_sort(&index->entries);
+ if ((position = index_find(index, path, stage)) < 0)
+ return position;
+
entry = git_vector_get(&index->entries, position);
if (entry != NULL)
git_tree_cache_invalidate_path(index->tree, entry->path);
@@ -584,45 +792,300 @@ int git_index_remove(git_index *index, int position)
return error;
}
+static int index_find(git_index *index, const char *path, int stage)
+{
+ struct entry_srch_key srch_key;
+
+ assert(path);
+
+ srch_key.path = path;
+ srch_key.stage = stage;
+
+ return git_vector_bsearch2(&index->entries, index->entries_search, &srch_key);
+}
+
int git_index_find(git_index *index, const char *path)
{
- return git_vector_bsearch2(&index->entries, index->entries_search, path);
+ int pos;
+
+ assert(index && path);
+
+ if ((pos = git_vector_bsearch2(&index->entries, index->entries_search_path, path)) < 0)
+ return pos;
+
+ /* Since our binary search only looked at path, we may be in the
+ * middle of a list of stages. */
+ while (pos > 0) {
+ git_index_entry *prev = git_vector_get(&index->entries, pos-1);
+
+ if (index->entries_cmp_path(prev->path, path) != 0)
+ break;
+
+ --pos;
+ }
+
+ return pos;
}
unsigned int git_index__prefix_position(git_index *index, const char *path)
{
+ struct entry_srch_key srch_key;
unsigned int pos;
- git_vector_bsearch3(&pos, &index->entries, index->entries_search, path);
+ srch_key.path = path;
+ srch_key.stage = 0;
+
+ git_vector_sort(&index->entries);
+ git_vector_bsearch3(
+ &pos, &index->entries, index->entries_search, &srch_key);
return pos;
}
-void git_index_uniq(git_index *index)
+int git_index_conflict_add(git_index *index,
+ const git_index_entry *ancestor_entry,
+ const git_index_entry *our_entry,
+ const git_index_entry *their_entry)
{
- git_vector_uniq(&index->entries);
+ git_index_entry *entries[3] = { 0 };
+ size_t i;
+ int ret = 0;
+
+ assert (index);
+
+ if ((ancestor_entry != NULL && (entries[0] = index_entry_dup(ancestor_entry)) == NULL) ||
+ (our_entry != NULL && (entries[1] = index_entry_dup(our_entry)) == NULL) ||
+ (their_entry != NULL && (entries[2] = index_entry_dup(their_entry)) == NULL))
+ return -1;
+
+ for (i = 0; i < 3; i++) {
+ if (entries[i] == NULL)
+ continue;
+
+ /* Make sure stage is correct */
+ entries[i]->flags = (entries[i]->flags & ~GIT_IDXENTRY_STAGEMASK) |
+ ((i+1) << GIT_IDXENTRY_STAGESHIFT);
+
+ if ((ret = index_insert(index, entries[i], 1)) < 0)
+ goto on_error;
+ }
+
+ return 0;
+
+on_error:
+ for (i = 0; i < 3; i++) {
+ if (entries[i] != NULL)
+ index_entry_free(entries[i]);
+ }
+
+ return ret;
}
-const git_index_entry_unmerged *git_index_get_unmerged_bypath(
+int git_index_conflict_get(git_index_entry **ancestor_out,
+ git_index_entry **our_out,
+ git_index_entry **their_out,
git_index *index, const char *path)
{
+ int pos, stage;
+ git_index_entry *conflict_entry;
+ int error = GIT_ENOTFOUND;
+
+ assert(ancestor_out && our_out && their_out && index && path);
+
+ *ancestor_out = NULL;
+ *our_out = NULL;
+ *their_out = NULL;
+
+ if ((pos = git_index_find(index, path)) < 0)
+ return pos;
+
+ while ((unsigned int)pos < git_index_entrycount(index)) {
+ conflict_entry = git_vector_get(&index->entries, pos);
+
+ if (index->entries_cmp_path(conflict_entry->path, path) != 0)
+ break;
+
+ stage = index_entry_stage(conflict_entry);
+
+ switch (stage) {
+ case 3:
+ *their_out = conflict_entry;
+ error = 0;
+ break;
+ case 2:
+ *our_out = conflict_entry;
+ error = 0;
+ break;
+ case 1:
+ *ancestor_out = conflict_entry;
+ error = 0;
+ break;
+ default:
+ break;
+ };
+
+ ++pos;
+ }
+
+ return error;
+}
+
+int git_index_conflict_remove(git_index *index, const char *path)
+{
int pos;
+ git_index_entry *conflict_entry;
+ int error = 0;
+
assert(index && path);
- if (!index->unmerged.length)
+ if ((pos = git_index_find(index, path)) < 0)
+ return pos;
+
+ while ((unsigned int)pos < git_index_entrycount(index)) {
+ conflict_entry = git_vector_get(&index->entries, pos);
+
+ if (index->entries_cmp_path(conflict_entry->path, path) != 0)
+ break;
+
+ if (index_entry_stage(conflict_entry) == 0) {
+ pos++;
+ continue;
+ }
+
+ error = git_vector_remove(&index->entries, (unsigned int)pos);
+
+ if (error >= 0)
+ index_entry_free(conflict_entry);
+ }
+
+ return error;
+}
+
+static int index_conflicts_match(git_vector *v, size_t idx)
+{
+ git_index_entry *entry = git_vector_get(v, idx);
+
+ if (index_entry_stage(entry) > 0) {
+ index_entry_free(entry);
+ return 1;
+ }
+
+ return 0;
+}
+
+void git_index_conflict_cleanup(git_index *index)
+{
+ assert(index);
+ git_vector_remove_matching(&index->entries, index_conflicts_match);
+}
+
+int git_index_has_conflicts(git_index *index)
+{
+ unsigned int i;
+ git_index_entry *entry;
+
+ assert(index);
+
+ git_vector_foreach(&index->entries, i, entry) {
+ if (index_entry_stage(entry) > 0)
+ return 1;
+ }
+
+ return 0;
+}
+
+unsigned int git_index_reuc_entrycount(git_index *index)
+{
+ assert(index);
+ return (unsigned int)index->reuc.length;
+}
+
+static int index_reuc_insert(git_index *index, git_index_reuc_entry *reuc, int replace)
+{
+ git_index_reuc_entry **existing = NULL;
+ int position;
+
+ assert(index && reuc && reuc->path != NULL);
+
+ if ((position = git_index_reuc_find(index, reuc->path)) >= 0)
+ existing = (git_index_reuc_entry **)&index->reuc.contents[position];
+
+ if (!replace || !existing)
+ return git_vector_insert(&index->reuc, reuc);
+
+ /* exists, replace it */
+ git__free((*existing)->path);
+ git__free(*existing);
+ *existing = reuc;
+
+ return 0;
+}
+
+int git_index_reuc_add(git_index *index, const char *path,
+ int ancestor_mode, git_oid *ancestor_oid,
+ int our_mode, git_oid *our_oid,
+ int their_mode, git_oid *their_oid)
+{
+ git_index_reuc_entry *reuc = NULL;
+ int error = 0;
+
+ assert(index && path);
+
+ if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 ||
+ (error = index_reuc_insert(index, reuc, 1)) < 0)
+ {
+ index_entry_reuc_free(reuc);
+ return error;
+ }
+
+ return error;
+}
+
+int git_index_reuc_find(git_index *index, const char *path)
+{
+ return git_vector_bsearch2(&index->reuc, index->reuc_search, path);
+}
+
+const git_index_reuc_entry *git_index_reuc_get_bypath(
+ git_index *index, const char *path)
+{
+ int pos;
+ assert(index && path);
+
+ if (!index->reuc.length)
return NULL;
- if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < 0)
+ git_vector_sort(&index->reuc);
+
+ if ((pos = git_index_reuc_find(index, path)) < 0)
return NULL;
- return git_vector_get(&index->unmerged, pos);
+ return git_vector_get(&index->reuc, pos);
}
-const git_index_entry_unmerged *git_index_get_unmerged_byindex(
+const git_index_reuc_entry *git_index_reuc_get_byindex(
git_index *index, size_t n)
{
assert(index);
- return git_vector_get(&index->unmerged, n);
+
+ git_vector_sort(&index->reuc);
+ return git_vector_get(&index->reuc, n);
+}
+
+int git_index_reuc_remove(git_index *index, int position)
+{
+ int error;
+ git_index_reuc_entry *reuc;
+
+ git_vector_sort(&index->reuc);
+
+ reuc = git_vector_get(&index->reuc, position);
+ error = git_vector_remove(&index->reuc, (unsigned int)position);
+
+ if (!error)
+ index_entry_reuc_free(reuc);
+
+ return error;
}
static int index_error_invalid(const char *message)
@@ -631,26 +1094,27 @@ static int index_error_invalid(const char *message)
return -1;
}
-static int read_unmerged(git_index *index, const char *buffer, size_t size)
+static int read_reuc(git_index *index, const char *buffer, size_t size)
{
const char *endptr;
size_t len;
int i;
- if (git_vector_init(&index->unmerged, 16, unmerged_cmp) < 0)
+ /* This gets called multiple times, the vector might already be initialized */
+ if (index->reuc._alloc_size == 0 && git_vector_init(&index->reuc, 16, reuc_cmp) < 0)
return -1;
while (size) {
- git_index_entry_unmerged *lost;
+ git_index_reuc_entry *lost;
len = strlen(buffer) + 1;
if (size <= len)
- return index_error_invalid("reading unmerged entries");
+ return index_error_invalid("reading reuc entries");
- lost = git__malloc(sizeof(git_index_entry_unmerged));
+ lost = git__malloc(sizeof(git_index_reuc_entry));
GITERR_CHECK_ALLOC(lost);
- if (git_vector_insert(&index->unmerged, lost) < 0)
+ if (git_vector_insert(&index->reuc, lost) < 0)
return -1;
/* read NUL-terminated pathname for entry */
@@ -667,13 +1131,13 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size)
if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 ||
!endptr || endptr == buffer || *endptr ||
(unsigned)tmp > UINT_MAX)
- return index_error_invalid("reading unmerged entry stage");
+ return index_error_invalid("reading reuc entry stage");
lost->mode[i] = tmp;
len = (endptr + 1) - buffer;
if (size <= len)
- return index_error_invalid("reading unmerged entry stage");
+ return index_error_invalid("reading reuc entry stage");
size -= len;
buffer += len;
@@ -684,7 +1148,7 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size)
if (!lost->mode[i])
continue;
if (size < 20)
- return index_error_invalid("reading unmerged entry oid");
+ return index_error_invalid("reading reuc entry oid");
git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer);
size -= 20;
@@ -692,6 +1156,9 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size)
}
}
+ /* entries are guaranteed to be sorted on-disk */
+ index->reuc.sorted = 1;
+
return 0;
}
@@ -797,7 +1264,7 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer
if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < 0)
return 0;
} else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) {
- if (read_unmerged(index, buffer + 8, dest.extension_size) < 0)
+ if (read_reuc(index, buffer + 8, dest.extension_size) < 0)
return 0;
}
/* else, unsupported extension. We cannot parse this, but we can skip
@@ -996,6 +1463,69 @@ static int write_entries(git_index *index, git_filebuf *file)
return error;
}
+static int write_extension(git_filebuf *file, struct index_extension *header, git_buf *data)
+{
+ struct index_extension ondisk;
+ int error = 0;
+
+ memset(&ondisk, 0x0, sizeof(struct index_extension));
+ memcpy(&ondisk, header, 4);
+ ondisk.extension_size = htonl(header->extension_size);
+
+ if ((error = git_filebuf_write(file, &ondisk, sizeof(struct index_extension))) == 0)
+ error = git_filebuf_write(file, data->ptr, data->size);
+
+ return error;
+}
+
+static int create_reuc_extension_data(git_buf *reuc_buf, git_index_reuc_entry *reuc)
+{
+ int i;
+ int error = 0;
+
+ if ((error = git_buf_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0)
+ return error;
+
+ for (i = 0; i < 3; i++) {
+ if ((error = git_buf_printf(reuc_buf, "%o", reuc->mode[i])) < 0 ||
+ (error = git_buf_put(reuc_buf, "\0", 1)) < 0)
+ return error;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (reuc->mode[i] && (error = git_buf_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0)
+ return error;
+ }
+
+ return 0;
+}
+
+static int write_reuc_extension(git_index *index, git_filebuf *file)
+{
+ git_buf reuc_buf = GIT_BUF_INIT;
+ git_vector *out = &index->reuc;
+ git_index_reuc_entry *reuc;
+ struct index_extension extension;
+ unsigned int i;
+ int error = 0;
+
+ git_vector_foreach(out, i, reuc) {
+ if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0)
+ goto done;
+ }
+
+ memset(&extension, 0x0, sizeof(struct index_extension));
+ memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4);
+ extension.extension_size = reuc_buf.size;
+
+ error = write_extension(file, &extension, &reuc_buf);
+
+ git_buf_free(&reuc_buf);
+
+done:
+ return error;
+}
+
static int write_index(git_index *index, git_filebuf *file)
{
git_oid hash_final;
@@ -1018,7 +1548,11 @@ static int write_index(git_index *index, git_filebuf *file)
if (write_entries(index, file) < 0)
return -1;
- /* TODO: write extensions (tree cache) */
+ /* TODO: write tree cache extension */
+
+ /* write the reuc extension */
+ if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0)
+ return -1;
/* get out the hash for all the contents we've appended to the file */
git_filebuf_hash(&hash_final, file);
@@ -1029,22 +1563,20 @@ static int write_index(git_index *index, git_filebuf *file)
int git_index_entry_stage(const git_index_entry *entry)
{
- return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT;
+ return index_entry_stage(entry);
}
typedef struct read_tree_data {
git_index *index;
- git_indexer_stats *stats;
+ git_transfer_progress *stats;
} read_tree_data;
static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data)
{
- read_tree_data *rtd = data;
+ git_index *index = (git_index *)data;
git_index_entry *entry = NULL;
git_buf path = GIT_BUF_INIT;
- rtd->stats->total++;
-
if (git_tree_entry__is_tree(tentry))
return 0;
@@ -1059,7 +1591,7 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
entry->path = git_buf_detach(&path);
git_buf_free(&path);
- if (index_insert(rtd->index, entry, 0) < 0) {
+ if (index_insert(index, entry, 0) < 0) {
index_entry_free(entry);
return -1;
}
@@ -1067,16 +1599,65 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da
return 0;
}
-int git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats)
+int git_index_read_tree(git_index *index, git_tree *tree)
{
- git_indexer_stats dummy_stats;
- read_tree_data rtd = {index, NULL};
+ git_index_clear(index);
- if (!stats) stats = &dummy_stats;
- stats->total = 0;
- rtd.stats = stats;
+ return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index);
+}
- git_index_clear(index);
+git_repository *git_index_owner(const git_index *index)
+{
+ return INDEX_OWNER(index);
+}
- return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, &rtd);
+int git_index_read_tree_match(
+ git_index *index, git_tree *tree, git_strarray *strspec)
+{
+#if 0
+ git_iterator *iter = NULL;
+ const git_index_entry *entry;
+ char *pfx = NULL;
+ git_vector pathspec = GIT_VECTOR_INIT;
+ git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;
+#endif
+
+ if (!git_pathspec_is_interesting(strspec))
+ return git_index_read_tree(index, tree);
+
+ return git_index_read_tree(index, tree);
+
+#if 0
+ /* The following loads the matches into the index, but doesn't
+ * erase obsoleted entries (e.g. you load a blob at "a/b" which
+ * should obsolete a blob at "a/b/c/d" since b is no longer a tree)
+ */
+
+ if (git_pathspec_init(&pathspec, strspec, &pathpool) < 0)
+ return -1;
+
+ pfx = git_pathspec_prefix(strspec);
+
+ if ((error = git_iterator_for_tree_range(
+ &iter, INDEX_OWNER(index), tree, pfx, pfx)) < 0 ||
+ (error = git_iterator_current(iter, &entry)) < 0)
+ goto cleanup;
+
+ while (entry != NULL) {
+ if (git_pathspec_match_path(&pathspec, entry->path, false, false) &&
+ (error = git_index_add(index, entry)) < 0)
+ goto cleanup;
+
+ if ((error = git_iterator_advance(iter, &entry)) < 0)
+ goto cleanup;
+ }
+
+cleanup:
+ git_iterator_free(iter);
+ git_pathspec_free(&pathspec);
+ git_pool_clear(&pathpool);
+ git__free(pfx);
+
+ return error;
+#endif
}
diff --git a/src/index.h b/src/index.h
index 7dd23ee60..f0dcd64d5 100644
--- a/src/index.h
+++ b/src/index.h
@@ -22,7 +22,7 @@ struct git_index {
char *index_file_path;
- time_t last_modified;
+ git_futils_filestamp stamp;
git_vector entries;
unsigned int on_disk:1;
@@ -33,13 +33,22 @@ struct git_index {
git_tree_cache *tree;
- git_vector unmerged;
+ git_vector reuc;
+ git_vector_cmp entries_cmp_path;
git_vector_cmp entries_search;
+ git_vector_cmp entries_search_path;
+ git_vector_cmp reuc_search;
};
-extern void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry);
+extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st);
extern unsigned int git_index__prefix_position(git_index *index, const char *path);
+extern int git_index_entry__cmp(const void *a, const void *b);
+extern int git_index_entry__cmp_icase(const void *a, const void *b);
+
+extern int git_index_read_tree_match(
+ git_index *index, git_tree *tree, git_strarray *strspec);
+
#endif
diff --git a/src/indexer.c b/src/indexer.c
index 7d4e18d7a..e9f235a72 100644
--- a/src/indexer.c
+++ b/src/indexer.c
@@ -17,7 +17,6 @@
#include "posix.h"
#include "pack.h"
#include "filebuf.h"
-#include "sha1.h"
#define UINT31_MAX (0x7FFFFFFF)
@@ -49,6 +48,8 @@ struct git_indexer_stream {
git_vector deltas;
unsigned int fanout[256];
git_oid hash;
+ git_transfer_progress_callback progress_cb;
+ void *progress_payload;
};
struct delta_info {
@@ -138,7 +139,11 @@ static int cache_cmp(const void *a, const void *b)
return git_oid_cmp(&ea->sha1, &eb->sha1);
}
-int git_indexer_stream_new(git_indexer_stream **out, const char *prefix)
+int git_indexer_stream_new(
+ git_indexer_stream **out,
+ const char *prefix,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
{
git_indexer_stream *idx;
git_buf path = GIT_BUF_INIT;
@@ -147,6 +152,8 @@ int git_indexer_stream_new(git_indexer_stream **out, const char *prefix)
idx = git__calloc(1, sizeof(git_indexer_stream));
GITERR_CHECK_ALLOC(idx);
+ idx->progress_cb = progress_cb;
+ idx->progress_payload = progress_payload;
error = git_buf_joinpath(&path, prefix, suff);
if (error < 0)
@@ -242,8 +249,10 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent
git_oid_cpy(&pentry->sha1, &oid);
pentry->offset = entry_start;
- if (git_vector_insert(&idx->pack->cache, pentry) < 0)
+ if (git_vector_insert(&idx->pack->cache, pentry) < 0) {
+ git__free(pentry);
goto on_error;
+ }
git_oid_cpy(&entry->oid, &oid);
entry->crc = crc32(0L, Z_NULL, 0);
@@ -268,12 +277,17 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent
on_error:
git__free(entry);
- git__free(pentry);
git__free(obj->data);
return -1;
}
-int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats)
+static void do_progress_callback(git_indexer_stream *idx, git_transfer_progress *stats)
+{
+ if (!idx->progress_cb) return;
+ idx->progress_cb(stats, idx->progress_payload);
+}
+
+int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats)
{
int error;
struct git_pack_header hdr;
@@ -282,7 +296,7 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
assert(idx && data && stats);
- processed = stats->processed;
+ processed = stats->indexed_objects;
if (git_filebuf_write(&idx->pack_file, data, size) < 0)
return -1;
@@ -324,8 +338,10 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0)
return -1;
- memset(stats, 0, sizeof(git_indexer_stats));
- stats->total = (unsigned int)idx->nr_objects;
+ stats->received_objects = 0;
+ stats->indexed_objects = 0;
+ stats->total_objects = (unsigned int)idx->nr_objects;
+ do_progress_callback(idx, stats);
}
/* Now that we have data in the pack, let's try to parse it */
@@ -361,7 +377,8 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
if (error < 0)
return error;
- stats->received++;
+ stats->received_objects++;
+ do_progress_callback(idx, stats);
continue;
}
@@ -379,8 +396,9 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz
git__free(obj.data);
- stats->processed = (unsigned int)++processed;
- stats->received++;
+ stats->indexed_objects = (unsigned int)++processed;
+ stats->received_objects++;
+ do_progress_callback(idx, stats);
}
return 0;
@@ -412,7 +430,7 @@ static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char
return git_buf_oom(path) ? -1 : 0;
}
-static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats)
+static int resolve_deltas(git_indexer_stream *idx, git_transfer_progress *stats)
{
unsigned int i;
struct delta_info *delta;
@@ -428,13 +446,14 @@ static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats)
return -1;
git__free(obj.data);
- stats->processed++;
+ stats->indexed_objects++;
+ do_progress_callback(idx, stats);
}
return 0;
}
-int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats)
+int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats)
{
git_mwindow *w = NULL;
unsigned int i, long_offsets = 0, left;
@@ -443,7 +462,10 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
struct entry *entry;
void *packfile_hash;
git_oid file_hash;
- SHA_CTX ctx;
+ git_hash_ctx ctx;
+
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
/* Test for this before resolve_deltas(), as it plays with idx->off */
if (idx->off < idx->pack->mwf.size - GIT_OID_RAWSZ) {
@@ -455,7 +477,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
if (resolve_deltas(idx, stats) < 0)
return -1;
- if (stats->processed != stats->total) {
+ if (stats->indexed_objects != stats->total_objects) {
giterr_set(GITERR_INDEXER, "Indexing error: early EOF");
return -1;
}
@@ -483,12 +505,11 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat
}
/* Write out the object names (SHA-1 hashes) */
- SHA1_Init(&ctx);
git_vector_foreach(&idx->objects, i, entry) {
git_filebuf_write(&idx->index_file, &entry->oid, sizeof(git_oid));
- SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ);
+ git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ);
}
- SHA1_Final(idx->hash.id, &ctx);
+ git_hash_final(&idx->hash, &ctx);
/* Write out the CRC32 values */
git_vector_foreach(&idx->objects, i, entry) {
@@ -563,6 +584,7 @@ on_error:
p_close(idx->pack->mwf.fd);
git_filebuf_cleanup(&idx->index_file);
git_buf_free(&filename);
+ git_hash_ctx_cleanup(&ctx);
return -1;
}
@@ -663,7 +685,10 @@ int git_indexer_write(git_indexer *idx)
struct entry *entry;
void *packfile_hash;
git_oid file_hash;
- SHA_CTX ctx;
+ git_hash_ctx ctx;
+
+ if (git_hash_ctx_init(&ctx) < 0)
+ return -1;
git_vector_sort(&idx->objects);
@@ -693,14 +718,14 @@ int git_indexer_write(git_indexer *idx)
}
/* Write out the object names (SHA-1 hashes) */
- SHA1_Init(&ctx);
git_vector_foreach(&idx->objects, i, entry) {
- error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid));
- SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ);
- if (error < 0)
+ if ((error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid))) < 0 ||
+ (error = git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ)) < 0)
goto cleanup;
}
- SHA1_Final(idx->hash.id, &ctx);
+
+ if ((error = git_hash_final(&idx->hash, &ctx)) < 0)
+ goto cleanup;
/* Write out the CRC32 values */
git_vector_foreach(&idx->objects, i, entry) {
@@ -778,11 +803,12 @@ cleanup:
if (error < 0)
git_filebuf_cleanup(&idx->file);
git_buf_free(&filename);
+ git_hash_ctx_cleanup(&ctx);
return error;
}
-int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)
+int git_indexer_run(git_indexer *idx, git_transfer_progress *stats)
{
git_mwindow_file *mwf;
git_off_t off = sizeof(struct git_pack_header);
@@ -797,8 +823,8 @@ int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)
if (error < 0)
return error;
- stats->total = (unsigned int)idx->nr_objects;
- stats->processed = processed = 0;
+ stats->total_objects = (unsigned int)idx->nr_objects;
+ stats->indexed_objects = processed = 0;
while (processed < idx->nr_objects) {
git_rawobj obj;
@@ -868,7 +894,7 @@ int git_indexer_run(git_indexer *idx, git_indexer_stats *stats)
git__free(obj.data);
- stats->processed = ++processed;
+ stats->indexed_objects = ++processed;
}
cleanup:
diff --git a/src/iterator.c b/src/iterator.c
index df6da9a87..ee83a4fda 100644
--- a/src/iterator.c
+++ b/src/iterator.c
@@ -330,13 +330,14 @@ typedef struct {
git_iterator base;
git_index *index;
unsigned int current;
+ bool free_index;
} index_iterator;
static int index_iterator__current(
git_iterator *self, const git_index_entry **entry)
{
index_iterator *ii = (index_iterator *)self;
- git_index_entry *ie = git_index_get(ii->index, ii->current);
+ git_index_entry *ie = git_index_get_byindex(ii->index, ii->current);
if (ie != NULL &&
ii->base.end != NULL &&
@@ -387,32 +388,47 @@ static int index_iterator__reset(git_iterator *self)
static void index_iterator__free(git_iterator *self)
{
index_iterator *ii = (index_iterator *)self;
- git_index_free(ii->index);
+ if (ii->free_index)
+ git_index_free(ii->index);
ii->index = NULL;
}
int git_iterator_for_index_range(
git_iterator **iter,
- git_repository *repo,
+ git_index *index,
const char *start,
const char *end)
{
- int error;
index_iterator *ii;
ITERATOR_BASE_INIT(ii, index, INDEX);
- if ((error = git_repository_index(&ii->index, repo)) < 0)
- git__free(ii);
- else {
- ii->base.ignore_case = ii->index->ignore_case;
- ii->current = start ? git_index__prefix_position(ii->index, start) : 0;
- *iter = (git_iterator *)ii;
- }
+ ii->index = index;
+ ii->base.ignore_case = ii->index->ignore_case;
+ ii->current = start ? git_index__prefix_position(ii->index, start) : 0;
- return error;
+ *iter = (git_iterator *)ii;
+
+ return 0;
}
+int git_iterator_for_repo_index_range(
+ git_iterator **iter,
+ git_repository *repo,
+ const char *start,
+ const char *end)
+{
+ int error;
+ git_index *index;
+
+ if ((error = git_repository_index(&index, repo)) < 0)
+ return error;
+
+ if (!(error = git_iterator_for_index_range(iter, index, start, end)))
+ ((index_iterator *)(*iter))->free_index = true;
+
+ return error;
+}
typedef struct workdir_iterator_frame workdir_iterator_frame;
struct workdir_iterator_frame {
@@ -641,26 +657,23 @@ static int workdir_iterator__update_entry(workdir_iterator *wi)
wi->entry.path = ps->path;
- /* skip over .git entry */
+ /* skip over .git entries */
if (STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT "/") == 0 ||
STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT) == 0)
return workdir_iterator__advance((git_iterator *)wi, NULL);
- /* if there is an error processing the entry, treat as ignored */
- wi->is_ignored = 1;
+ wi->is_ignored = -1;
- git_index__init_entry_from_stat(&ps->st, &wi->entry);
+ git_index_entry__init_from_stat(&wi->entry, &ps->st);
/* need different mode here to keep directories during iteration */
wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
/* if this is a file type we don't handle, treat as ignored */
- if (wi->entry.mode == 0)
+ if (wi->entry.mode == 0) {
+ wi->is_ignored = 1;
return 0;
-
- /* okay, we are far enough along to look up real ignore rule */
- if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)
- return 0; /* if error, ignore it and ignore file */
+ }
/* detect submodules */
if (S_ISDIR(wi->entry.mode)) {
@@ -693,24 +706,21 @@ int git_iterator_for_workdir_range(
assert(iter && repo);
- if ((error = git_repository__ensure_not_bare(repo, "scan working directory")) < 0)
+ if ((error = git_repository__ensure_not_bare(
+ repo, "scan working directory")) < 0)
return error;
ITERATOR_BASE_INIT(wi, workdir, WORKDIR);
-
wi->repo = repo;
- if ((error = git_repository_index(&index, repo)) < 0) {
+ if ((error = git_repository_index__weakptr(&index, repo)) < 0) {
git__free(wi);
return error;
}
- /* Set the ignore_case flag for the workdir iterator to match
- * that of the index. */
+ /* Match ignore_case flag for iterator to that of the index */
wi->base.ignore_case = index->ignore_case;
- git_index_free(index);
-
if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 ||
git_path_to_dir(&wi->path) < 0 ||
git_ignore__for_path(repo, "", &wi->ignores) < 0)
@@ -908,8 +918,18 @@ notfound:
int git_iterator_current_is_ignored(git_iterator *iter)
{
- return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :
- ((workdir_iterator *)iter)->is_ignored;
+ workdir_iterator *wi = (workdir_iterator *)iter;
+
+ if (iter->type != GIT_ITERATOR_WORKDIR)
+ return 0;
+
+ if (wi->is_ignored != -1)
+ return wi->is_ignored;
+
+ if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0)
+ wi->is_ignored = 1;
+
+ return wi->is_ignored;
}
int git_iterator_advance_into_directory(
diff --git a/src/iterator.h b/src/iterator.h
index d7df50137..77ead76cc 100644
--- a/src/iterator.h
+++ b/src/iterator.h
@@ -52,13 +52,23 @@ GIT_INLINE(int) git_iterator_for_tree(
}
extern int git_iterator_for_index_range(
- git_iterator **iter, git_repository *repo,
+ git_iterator **iter, git_index *index,
const char *start, const char *end);
GIT_INLINE(int) git_iterator_for_index(
+ git_iterator **iter, git_index *index)
+{
+ return git_iterator_for_index_range(iter, index, NULL, NULL);
+}
+
+extern int git_iterator_for_repo_index_range(
+ git_iterator **iter, git_repository *repo,
+ const char *start, const char *end);
+
+GIT_INLINE(int) git_iterator_for_repo_index(
git_iterator **iter, git_repository *repo)
{
- return git_iterator_for_index_range(iter, repo, NULL, NULL);
+ return git_iterator_for_repo_index_range(iter, repo, NULL, NULL);
}
extern int git_iterator_for_workdir_range(
diff --git a/src/merge.c b/src/merge.c
new file mode 100644
index 000000000..135af6a8c
--- /dev/null
+++ b/src/merge.c
@@ -0,0 +1,48 @@
+/*
+ * 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 "repository.h"
+#include "buffer.h"
+#include "merge.h"
+#include "refs.h"
+#include "git2/repository.h"
+#include "git2/merge.h"
+#include "git2/reset.h"
+
+int git_merge__cleanup(git_repository *repo)
+{
+ int error = 0;
+ git_buf merge_head_path = GIT_BUF_INIT,
+ merge_mode_path = GIT_BUF_INIT,
+ merge_msg_path = GIT_BUF_INIT;
+
+ assert(repo);
+
+ if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 ||
+ git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 ||
+ git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0)
+ return -1;
+
+ if (git_path_isfile(merge_head_path.ptr)) {
+ if ((error = p_unlink(merge_head_path.ptr)) < 0)
+ goto cleanup;
+ }
+
+ if (git_path_isfile(merge_mode_path.ptr))
+ (void)p_unlink(merge_mode_path.ptr);
+
+ if (git_path_isfile(merge_msg_path.ptr))
+ (void)p_unlink(merge_msg_path.ptr);
+
+cleanup:
+ git_buf_free(&merge_msg_path);
+ git_buf_free(&merge_mode_path);
+ git_buf_free(&merge_head_path);
+
+ return error;
+}
+
diff --git a/src/merge.h b/src/merge.h
new file mode 100644
index 000000000..2117d9214
--- /dev/null
+++ b/src/merge.h
@@ -0,0 +1,19 @@
+/*
+ * 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_merge_h__
+#define INCLUDE_merge_h__
+
+#include "git2/types.h"
+
+#define GIT_MERGE_MSG_FILE "MERGE_MSG"
+#define GIT_MERGE_MODE_FILE "MERGE_MODE"
+
+#define MERGE_CONFIG_FILE_MODE 0666
+
+int git_merge__cleanup(git_repository *repo);
+
+#endif
diff --git a/src/netops.c b/src/netops.c
index df502e619..422ea63f1 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -14,12 +14,13 @@
#else
# include <ws2tcpip.h>
# ifdef _MSC_VER
-# pragma comment(lib, "ws2_32.lib")
+# pragma comment(lib, "ws2_32")
# endif
#endif
#ifdef GIT_SSL
# include <openssl/ssl.h>
+# include <openssl/err.h>
# include <openssl/x509v3.h>
#endif
@@ -30,7 +31,6 @@
#include "netops.h"
#include "posix.h"
#include "buffer.h"
-#include "transport.h"
#ifdef GIT_WIN32
static void net_set_error(const char *str)
@@ -41,6 +41,8 @@ static void net_set_error(const char *str)
size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
0, error, 0, (LPSTR)&err_str, 0, 0);
+ GIT_UNUSED(size);
+
giterr_set(GITERR_NET, "%s: %s", str, err_str);
LocalFree(err_str);
}
@@ -108,8 +110,8 @@ static int gitno__recv_ssl(gitno_buffer *buf)
int ret;
do {
- ret = SSL_read(buf->ssl->ssl, buf->data + buf->offset, buf->len - buf->offset);
- } while (SSL_get_error(buf->ssl->ssl, ret) == SSL_ERROR_WANT_READ);
+ ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset);
+ } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ);
if (ret < 0) {
net_set_error("Error receiving socket data");
@@ -121,11 +123,11 @@ static int gitno__recv_ssl(gitno_buffer *buf)
}
#endif
-int gitno__recv(gitno_buffer *buf)
+static int gitno__recv(gitno_buffer *buf)
{
int ret;
- ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0);
+ ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0);
if (ret < 0) {
net_set_error("Error receiving socket data");
return -1;
@@ -136,31 +138,31 @@ int gitno__recv(gitno_buffer *buf)
}
void gitno_buffer_setup_callback(
- git_transport *t,
+ gitno_socket *socket,
gitno_buffer *buf,
char *data,
size_t len,
int (*recv)(gitno_buffer *buf), void *cb_data)
{
- memset(buf, 0x0, sizeof(gitno_buffer));
memset(data, 0x0, len);
buf->data = data;
buf->len = len;
buf->offset = 0;
- buf->fd = t->socket;
+ buf->socket = socket;
buf->recv = recv;
buf->cb_data = cb_data;
}
-void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len)
+void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)
{
#ifdef GIT_SSL
- if (t->use_ssl) {
- gitno_buffer_setup_callback(t, buf, data, len, gitno__recv_ssl, NULL);
- buf->ssl = &t->ssl;
- } else
+ if (socket->ssl.ctx) {
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
+ return;
+ }
#endif
- gitno_buffer_setup_callback(t, buf, data, len, gitno__recv, NULL);
+
+ gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);
}
/* Consume up to ptr and move the rest of the buffer to the beginning */
@@ -186,31 +188,26 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons)
buf->offset -= cons;
}
-int gitno_ssl_teardown(git_transport *t)
-{
#ifdef GIT_SSL
- int ret;
-#endif
-
- if (!t->use_ssl)
- return 0;
-#ifdef GIT_SSL
+static int gitno_ssl_teardown(gitno_ssl *ssl)
+{
+ int ret;
do {
- ret = SSL_shutdown(t->ssl.ssl);
+ ret = SSL_shutdown(ssl->ssl);
} while (ret == 0);
+
if (ret < 0)
- return ssl_set_error(&t->ssl, ret);
+ ret = ssl_set_error(ssl, ret);
+ else
+ ret = 0;
- SSL_free(t->ssl.ssl);
- SSL_CTX_free(t->ssl.ctx);
-#endif
- return 0;
+ SSL_free(ssl->ssl);
+ SSL_CTX_free(ssl->ctx);
+ return ret;
}
-
-#ifdef GIT_SSL
/* Match host names according to RFC 2818 rules */
static int match_host(const char *pattern, const char *host)
{
@@ -261,7 +258,7 @@ static int check_host_name(const char *name, const char *host)
return 0;
}
-static int verify_server_cert(git_transport *t, const char *host)
+static int verify_server_cert(gitno_ssl *ssl, const char *host)
{
X509 *cert;
X509_NAME *peer_name;
@@ -274,24 +271,24 @@ static int verify_server_cert(git_transport *t, const char *host)
void *addr;
int i = -1,j;
- if (SSL_get_verify_result(t->ssl.ssl) != X509_V_OK) {
+ if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
giterr_set(GITERR_SSL, "The SSL certificate is invalid");
return -1;
}
/* Try to parse the host as an IP address to see if it is */
- if (inet_pton(AF_INET, host, &addr4)) {
+ if (p_inet_pton(AF_INET, host, &addr4)) {
type = GEN_IPADD;
addr = &addr4;
} else {
- if(inet_pton(AF_INET6, host, &addr6)) {
+ if(p_inet_pton(AF_INET6, host, &addr6)) {
type = GEN_IPADD;
addr = &addr6;
}
}
- cert = SSL_get_peer_certificate(t->ssl.ssl);
+ cert = SSL_get_peer_certificate(ssl->ssl);
/* Check the alternative names */
alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
@@ -375,7 +372,7 @@ static int verify_server_cert(git_transport *t, const char *host)
on_error:
OPENSSL_free(peer_cn);
- return ssl_set_error(&t->ssl, 0);
+ return ssl_set_error(ssl, 0);
cert_fail:
OPENSSL_free(peer_cn);
@@ -383,51 +380,81 @@ cert_fail:
return -1;
}
-static int ssl_setup(git_transport *t, const char *host)
+static int ssl_setup(gitno_socket *socket, const char *host, int flags)
{
int ret;
SSL_library_init();
SSL_load_error_strings();
- t->ssl.ctx = SSL_CTX_new(SSLv23_method());
- if (t->ssl.ctx == NULL)
- return ssl_set_error(&t->ssl, 0);
+ socket->ssl.ctx = SSL_CTX_new(SSLv23_method());
+ if (socket->ssl.ctx == NULL)
+ return ssl_set_error(&socket->ssl, 0);
- SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY);
- SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_NONE, NULL);
- if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx))
- return ssl_set_error(&t->ssl, 0);
+ SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY);
+ SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL);
+ if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx))
+ return ssl_set_error(&socket->ssl, 0);
- t->ssl.ssl = SSL_new(t->ssl.ctx);
- if (t->ssl.ssl == NULL)
- return ssl_set_error(&t->ssl, 0);
+ socket->ssl.ssl = SSL_new(socket->ssl.ctx);
+ if (socket->ssl.ssl == NULL)
+ return ssl_set_error(&socket->ssl, 0);
- if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0)
- return ssl_set_error(&t->ssl, ret);
+ if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)
+ return ssl_set_error(&socket->ssl, ret);
- if ((ret = SSL_connect(t->ssl.ssl)) <= 0)
- return ssl_set_error(&t->ssl, ret);
+ if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
+ return ssl_set_error(&socket->ssl, ret);
- if (t->check_cert && verify_server_cert(t, host) < 0)
+ if ((GITNO_CONNECT_SSL_NO_CHECK_CERT & flags) || verify_server_cert(&socket->ssl, host) < 0)
return -1;
return 0;
}
-#else
-static int ssl_setup(git_transport *t, const char *host)
+#endif
+
+static int gitno__close(GIT_SOCKET s)
{
- GIT_UNUSED(t);
- GIT_UNUSED(host);
+#ifdef GIT_WIN32
+ if (SOCKET_ERROR == closesocket(s))
+ return -1;
+
+ if (0 != WSACleanup()) {
+ giterr_set(GITERR_OS, "Winsock cleanup failed");
+ return -1;
+ }
+
return 0;
-}
+#else
+ return close(s);
#endif
+}
-int gitno_connect(git_transport *t, const char *host, const char *port)
+int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags)
{
struct addrinfo *info = NULL, *p;
struct addrinfo hints;
- int ret;
GIT_SOCKET s = INVALID_SOCKET;
+ int ret;
+
+#ifdef GIT_WIN32
+ /* on win32, the WSA context needs to be initialized
+ * before any socket calls can be performed */
+ WSADATA wsd;
+
+ if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
+ giterr_set(GITERR_OS, "Winsock init failed");
+ return -1;
+ }
+
+ if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
+ WSACleanup();
+ giterr_set(GITERR_OS, "Winsock init failed");
+ return -1;
+ }
+#endif
+
+ /* Zero the socket structure provided */
+ memset(s_out, 0x0, sizeof(gitno_socket));
memset(&hints, 0x0, sizeof(struct addrinfo));
hints.ai_socktype = SOCK_STREAM;
@@ -451,7 +478,7 @@ int gitno_connect(git_transport *t, const char *host, const char *port)
break;
/* If we can't connect, try the next one */
- gitno_close(s);
+ gitno__close(s);
s = INVALID_SOCKET;
}
@@ -461,46 +488,56 @@ int gitno_connect(git_transport *t, const char *host, const char *port)
return -1;
}
- t->socket = s;
+ s_out->socket = s;
p_freeaddrinfo(info);
- if (t->use_ssl && ssl_setup(t, host) < 0)
+#ifdef GIT_SSL
+ if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0)
+ return -1;
+#else
+ /* SSL is not supported */
+ if (flags & GITNO_CONNECT_SSL) {
+ giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2.");
return -1;
+ }
+#endif
return 0;
}
#ifdef GIT_SSL
-static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len)
+static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags)
{
int ret;
size_t off = 0;
+ GIT_UNUSED(flags);
+
while (off < len) {
ret = SSL_write(ssl->ssl, msg + off, len - off);
if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE)
return ssl_set_error(ssl, ret);
off += ret;
- }
+ }
return off;
}
#endif
-int gitno_send(git_transport *t, const char *msg, size_t len, int flags)
+int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
{
int ret;
size_t off = 0;
#ifdef GIT_SSL
- if (t->use_ssl)
- return send_ssl(&t->ssl, msg, len);
+ if (socket->ssl.ctx)
+ return gitno_send_ssl(&socket->ssl, msg, len, flags);
#endif
while (off < len) {
errno = 0;
- ret = p_send(t->socket, msg + off, len - off, flags);
+ ret = p_send(socket->socket, msg + off, len - off, flags);
if (ret < 0) {
net_set_error("Error sending data");
return -1;
@@ -512,19 +549,17 @@ int gitno_send(git_transport *t, const char *msg, size_t len, int flags)
return (int)off;
}
-
-#ifdef GIT_WIN32
-int gitno_close(GIT_SOCKET s)
+int gitno_close(gitno_socket *s)
{
- return closesocket(s) == SOCKET_ERROR ? -1 : 0;
-}
-#else
-int gitno_close(GIT_SOCKET s)
-{
- return close(s);
-}
+#ifdef GIT_SSL
+ if (s->ssl.ctx &&
+ gitno_ssl_teardown(&s->ssl) < 0)
+ return -1;
#endif
+ return gitno__close(s->socket);
+}
+
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
{
fd_set fds;
@@ -534,10 +569,10 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
tv.tv_usec = usec;
FD_ZERO(&fds);
- FD_SET(buf->fd, &fds);
+ FD_SET(buf->socket->socket, &fds);
/* The select(2) interface is silly */
- return select((int)buf->fd + 1, &fds, NULL, NULL, &tv);
+ return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
}
int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)
diff --git a/src/netops.h b/src/netops.h
index 7c53fd0dc..efbbc65a4 100644
--- a/src/netops.h
+++ b/src/netops.h
@@ -10,31 +10,60 @@
#include "posix.h"
#include "common.h"
+#ifdef GIT_SSL
+# include <openssl/ssl.h>
+#endif
+
+struct gitno_ssl {
+#ifdef GIT_SSL
+ SSL_CTX *ctx;
+ SSL *ssl;
+#else
+ size_t dummy;
+#endif
+};
+
+typedef struct gitno_ssl gitno_ssl;
+
+/* Represents a socket that may or may not be using SSL */
+struct gitno_socket {
+ GIT_SOCKET socket;
+ gitno_ssl ssl;
+};
+
+typedef struct gitno_socket gitno_socket;
+
struct gitno_buffer {
char *data;
size_t len;
size_t offset;
- GIT_SOCKET fd;
-#ifdef GIT_SSL
- struct gitno_ssl *ssl;
-#endif
- int (*recv)(gitno_buffer *buffer);
+ gitno_socket *socket;
+ int (*recv)(struct gitno_buffer *buffer);
void *cb_data;
};
-void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len);
-void gitno_buffer_setup_callback(git_transport *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
+typedef struct gitno_buffer gitno_buffer;
+
+/* Flags to gitno_connect */
+enum {
+ /* Attempt to create an SSL connection. */
+ GITNO_CONNECT_SSL = 1,
+
+ /* Valid only when GITNO_CONNECT_SSL is also specified.
+ * Indicates that the server certificate should not be validated. */
+ GITNO_CONNECT_SSL_NO_CHECK_CERT = 2,
+};
+
+void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len);
+void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
int gitno_recv(gitno_buffer *buf);
-int gitno__recv(gitno_buffer *buf);
void gitno_consume(gitno_buffer *buf, const char *ptr);
void gitno_consume_n(gitno_buffer *buf, size_t cons);
-int gitno_connect(git_transport *t, const char *host, const char *port);
-int gitno_send(git_transport *t, const char *msg, size_t len, int flags);
-int gitno_close(GIT_SOCKET s);
-int gitno_ssl_teardown(git_transport *t);
-int gitno_send_chunk_size(int s, size_t len);
+int gitno_connect(gitno_socket *socket, const char *host, const char *port, int flags);
+int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags);
+int gitno_close(gitno_socket *s);
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec);
int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port);
diff --git a/src/odb.c b/src/odb.c
index 7c21598f0..9c602d1d2 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -23,6 +23,8 @@
#define GIT_LOOSE_PRIORITY 2
#define GIT_PACKED_PRIORITY 1
+#define GIT_ALTERNATES_MAX_DEPTH 5
+
typedef struct
{
git_odb_backend *backend;
@@ -30,6 +32,8 @@ typedef struct
int is_alternate;
} backend_internal;
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
+
static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type)
{
const char *type_str = git_object_type2string(obj_type);
@@ -117,23 +121,27 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type)
{
int hdr_len;
char hdr[64], buffer[2048];
- git_hash_ctx *ctx;
+ git_hash_ctx ctx;
ssize_t read_len = 0;
+ int error = 0;
if (!git_object_typeisloose(type)) {
giterr_set(GITERR_INVALID, "Invalid object type for hash");
return -1;
}
- hdr_len = format_object_header(hdr, sizeof(hdr), size, type);
+ if ((error = git_hash_ctx_init(&ctx)) < 0)
+ return -1;
- ctx = git_hash_new_ctx();
- GITERR_CHECK_ALLOC(ctx);
+ hdr_len = format_object_header(hdr, sizeof(hdr), size, type);
- git_hash_update(ctx, hdr, hdr_len);
+ if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0)
+ goto done;
while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
- git_hash_update(ctx, buffer, read_len);
+ if ((error = git_hash_update(&ctx, buffer, read_len)) < 0)
+ goto done;
+
size -= read_len;
}
@@ -141,15 +149,18 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type)
* If size is not zero, the file was truncated after we originally
* stat'd it, so we consider this a read failure too */
if (read_len < 0 || size > 0) {
- git_hash_free_ctx(ctx);
giterr_set(GITERR_OS, "Error reading file for hashing");
+ error = -1;
+
+ goto done;
return -1;
}
- git_hash_final(out, ctx);
- git_hash_free_ctx(ctx);
+ error = git_hash_final(out, &ctx);
- return 0;
+done:
+ git_hash_ctx_cleanup(&ctx);
+ return error;
}
int git_odb__hashfd_filtered(
@@ -388,7 +399,7 @@ int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority)
return add_backend_internal(odb, backend, priority, 1);
}
-static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates)
+static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates, int alternate_depth)
{
git_odb_backend *loose, *packed;
@@ -402,10 +413,10 @@ static int add_default_backends(git_odb *db, const char *objects_dir, int as_alt
add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates) < 0)
return -1;
- return 0;
+ return load_alternates(db, objects_dir, alternate_depth);
}
-static int load_alternates(git_odb *odb, const char *objects_dir)
+static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth)
{
git_buf alternates_path = GIT_BUF_INIT;
git_buf alternates_buf = GIT_BUF_INIT;
@@ -413,6 +424,11 @@ static int load_alternates(git_odb *odb, const char *objects_dir)
const char *alternate;
int result = 0;
+ /* Git reports an error, we just ignore anything deeper */
+ if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) {
+ return 0;
+ }
+
if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0)
return -1;
@@ -433,14 +449,18 @@ static int load_alternates(git_odb *odb, const char *objects_dir)
if (*alternate == '\0' || *alternate == '#')
continue;
- /* relative path: build based on the current `objects` folder */
- if (*alternate == '.') {
+ /*
+ * Relative path: build based on the current `objects`
+ * folder. However, relative paths are only allowed in
+ * the current repository.
+ */
+ if (*alternate == '.' && !alternate_depth) {
if ((result = git_buf_joinpath(&alternates_path, objects_dir, alternate)) < 0)
break;
alternate = git_buf_cstr(&alternates_path);
}
- if ((result = add_default_backends(odb, alternate, 1)) < 0)
+ if ((result = add_default_backends(odb, alternate, 1, alternate_depth + 1)) < 0)
break;
}
@@ -461,8 +481,7 @@ int git_odb_open(git_odb **out, const char *objects_dir)
if (git_odb_new(&db) < 0)
return -1;
- if (add_default_backends(db, objects_dir, 0) < 0 ||
- load_alternates(db, objects_dir) < 0)
+ if (add_default_backends(db, objects_dir, 0, 0) < 0)
{
git_odb_free(db);
return -1;
@@ -766,6 +785,31 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi
return error;
}
+int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload)
+{
+ unsigned int i;
+ int error = GIT_ERROR;
+
+ assert(out && db);
+
+ for (i = 0; i < db->backends.length && error < 0; ++i) {
+ backend_internal *internal = git_vector_get(&db->backends, i);
+ git_odb_backend *b = internal->backend;
+
+ /* we don't write in alternates! */
+ if (internal->is_alternate)
+ continue;
+
+ if (b->writepack != NULL)
+ error = b->writepack(out, b, progress_cb, progress_payload);
+ }
+
+ if (error == GIT_PASSTHROUGH)
+ error = 0;
+
+ return error;
+}
+
void * git_odb_backend_malloc(git_odb_backend *backend, size_t len)
{
GIT_UNUSED(backend);
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 964e82afb..9f7a6ee1f 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -26,6 +26,11 @@ struct pack_backend {
char *pack_folder;
};
+struct pack_writepack {
+ struct git_odb_writepack parent;
+ git_indexer_stream *indexer_stream;
+};
+
/**
* The wonderful tale of a Packed Object lookup query
* ===================================================
@@ -475,6 +480,67 @@ static int pack_backend__foreach(git_odb_backend *_backend, int (*cb)(git_oid *o
return 0;
}
+static int pack_backend__writepack_add(struct git_odb_writepack *_writepack, const void *data, size_t size, git_transfer_progress *stats)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ return git_indexer_stream_add(writepack->indexer_stream, data, size, stats);
+}
+
+static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_transfer_progress *stats)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ return git_indexer_stream_finalize(writepack->indexer_stream, stats);
+}
+
+static void pack_backend__writepack_free(struct git_odb_writepack *_writepack)
+{
+ struct pack_writepack *writepack = (struct pack_writepack *)_writepack;
+
+ assert(writepack);
+
+ git_indexer_stream_free(writepack->indexer_stream);
+ git__free(writepack);
+}
+
+static int pack_backend__writepack(struct git_odb_writepack **out,
+ git_odb_backend *_backend,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ struct pack_backend *backend;
+ struct pack_writepack *writepack;
+
+ assert(out && _backend);
+
+ *out = NULL;
+
+ backend = (struct pack_backend *)_backend;
+
+ writepack = git__calloc(1, sizeof(struct pack_writepack));
+ GITERR_CHECK_ALLOC(writepack);
+
+ if (git_indexer_stream_new(&writepack->indexer_stream,
+ backend->pack_folder, progress_cb, progress_payload) < 0) {
+ git__free(writepack);
+ return -1;
+ }
+
+ writepack->parent.backend = _backend;
+ writepack->parent.add = pack_backend__writepack_add;
+ writepack->parent.commit = pack_backend__writepack_commit;
+ writepack->parent.free = pack_backend__writepack_free;
+
+ *out = (git_odb_writepack *)writepack;
+
+ return 0;
+}
+
static void pack_backend__free(git_odb_backend *_backend)
{
struct pack_backend *backend;
@@ -553,6 +619,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir)
backend->parent.read_header = NULL;
backend->parent.exists = &pack_backend__exists;
backend->parent.foreach = &pack_backend__foreach;
+ backend->parent.writepack = &pack_backend__writepack;
backend->parent.free = &pack_backend__free;
*backend_out = (git_odb_backend *)backend;
diff --git a/src/pack-objects.c b/src/pack-objects.c
index eb76e05a2..a146dc048 100644
--- a/src/pack-objects.c
+++ b/src/pack-objects.c
@@ -73,16 +73,16 @@ static int packbuilder_config(git_packbuilder *pb)
{
git_config *config;
int ret;
+ int64_t val;
if (git_repository_config__weakptr(&config, pb->repo) < 0)
return -1;
-#define config_get(key, dst, default) \
- ret = git_config_get_int64((int64_t *)&dst, config, key); \
- if (ret == GIT_ENOTFOUND) \
- dst = default; \
- else if (ret < 0) \
- return -1;
+#define config_get(KEY,DST,DFLT) do { \
+ ret = git_config_get_int64(&val, config, KEY); \
+ if (!ret) (DST) = val; \
+ else if (ret == GIT_ENOTFOUND) (DST) = (DFLT); \
+ else if (ret < 0) return -1; } while (0)
config_get("pack.deltaCacheSize", pb->max_delta_cache_size,
GIT_PACK_DELTA_CACHE_SIZE);
@@ -103,7 +103,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
*out = NULL;
- pb = git__calloc(sizeof(*pb), 1);
+ pb = git__calloc(1, sizeof(*pb));
GITERR_CHECK_ALLOC(pb);
pb->object_ix = git_oidmap_alloc();
@@ -113,9 +113,8 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
pb->repo = repo;
pb->nr_threads = 1; /* do not spawn any thread by default */
- pb->ctx = git_hash_new_ctx();
- if (!pb->ctx ||
+ if (git_hash_ctx_init(&pb->ctx) < 0 ||
git_repository_odb(&pb->odb, repo) < 0 ||
packbuilder_config(pb) < 0)
goto on_error;
@@ -297,14 +296,13 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po)
if (git_buf_put(buf, (char *)hdr, hdr_len) < 0)
goto on_error;
- git_hash_update(pb->ctx, hdr, hdr_len);
+ if (git_hash_update(&pb->ctx, hdr, hdr_len) < 0)
+ goto on_error;
if (type == GIT_OBJ_REF_DELTA) {
- if (git_buf_put(buf, (char *)po->delta->id.id,
- GIT_OID_RAWSZ) < 0)
+ if (git_buf_put(buf, (char *)po->delta->id.id, GIT_OID_RAWSZ) < 0 ||
+ git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ) < 0)
goto on_error;
-
- git_hash_update(pb->ctx, po->delta->id.id, GIT_OID_RAWSZ);
}
/* Write data */
@@ -319,11 +317,10 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po)
size = zbuf.size;
}
- if (git_buf_put(buf, data, size) < 0)
+ if (git_buf_put(buf, data, size) < 0 ||
+ git_hash_update(&pb->ctx, data, size) < 0)
goto on_error;
- git_hash_update(pb->ctx, data, size);
-
if (po->delta_data)
git__free(po->delta_data);
@@ -573,7 +570,8 @@ static int write_pack(git_packbuilder *pb,
if (cb(&ph, sizeof(ph), data) < 0)
goto on_error;
- git_hash_update(pb->ctx, &ph, sizeof(ph));
+ if (git_hash_update(&pb->ctx, &ph, sizeof(ph)) < 0)
+ goto on_error;
pb->nr_remaining = pb->nr_objects;
do {
@@ -592,7 +590,9 @@ static int write_pack(git_packbuilder *pb,
git__free(write_order);
git_buf_free(&buf);
- git_hash_final(&pb->pack_oid, pb->ctx);
+
+ if (git_hash_final(&pb->pack_oid, &pb->ctx) < 0)
+ goto on_error;
return cb(pb->pack_oid.id, GIT_OID_RAWSZ, data);
@@ -604,8 +604,8 @@ on_error:
static int send_pack_file(void *buf, size_t size, void *data)
{
- git_transport *t = (git_transport *)data;
- return gitno_send(t, buf, size, 0);
+ 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)
@@ -1231,10 +1231,16 @@ static int prepare_pack(git_packbuilder *pb)
#define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; }
-int git_packbuilder_send(git_packbuilder *pb, git_transport *t)
+int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s)
{
PREPARE_PACK;
- return write_pack(pb, &send_pack_file, t);
+ 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;
+ return write_pack(pb, cb, payload);
}
int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb)
@@ -1286,6 +1292,16 @@ int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid)
return 0;
}
+uint32_t git_packbuilder_object_count(git_packbuilder *pb)
+{
+ return pb->nr_objects;
+}
+
+uint32_t git_packbuilder_written(git_packbuilder *pb)
+{
+ return pb->nr_written;
+}
+
void git_packbuilder_free(git_packbuilder *pb)
{
if (pb == NULL)
@@ -1302,14 +1318,13 @@ void git_packbuilder_free(git_packbuilder *pb)
if (pb->odb)
git_odb_free(pb->odb);
- if (pb->ctx)
- git_hash_free_ctx(pb->ctx);
-
if (pb->object_ix)
git_oidmap_free(pb->object_ix);
if (pb->object_list)
git__free(pb->object_list);
+ git_hash_ctx_cleanup(&pb->ctx);
+
git__free(pb);
}
diff --git a/src/pack-objects.h b/src/pack-objects.h
index 0d4854d0d..e34cc2754 100644
--- a/src/pack-objects.h
+++ b/src/pack-objects.h
@@ -13,6 +13,7 @@
#include "buffer.h"
#include "hash.h"
#include "oidmap.h"
+#include "netops.h"
#include "git2/oid.h"
@@ -51,7 +52,7 @@ struct git_packbuilder {
git_repository *repo; /* associated repository */
git_odb *odb; /* associated object database */
- git_hash_ctx *ctx;
+ git_hash_ctx ctx;
uint32_t nr_objects,
nr_alloc,
@@ -70,18 +71,18 @@ struct git_packbuilder {
git_cond progress_cond;
/* configs */
- unsigned long delta_cache_size;
- unsigned long max_delta_cache_size;
- unsigned long cache_max_small_delta_size;
- unsigned long big_file_threshold;
- unsigned long window_memory_limit;
+ uint64_t delta_cache_size;
+ uint64_t max_delta_cache_size;
+ uint64_t cache_max_small_delta_size;
+ uint64_t big_file_threshold;
+ uint64_t window_memory_limit;
int nr_threads; /* nr of threads to use */
bool done;
};
-int git_packbuilder_send(git_packbuilder *pb, git_transport *t);
+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/path.c b/src/path.c
index 09556bd3f..98351bec3 100644
--- a/src/path.c
+++ b/src/path.c
@@ -382,9 +382,10 @@ int git_path_walk_up(
iter.asize = path->asize;
while (scan >= stop) {
- if ((error = cb(data, &iter)) < 0)
- break;
+ error = cb(data, &iter);
iter.ptr[scan] = oldc;
+ if (error < 0)
+ break;
scan = git_buf_rfind_next(&iter, '/');
if (scan >= 0) {
scan++;
diff --git a/src/pathspec.c b/src/pathspec.c
new file mode 100644
index 000000000..fc6547afe
--- /dev/null
+++ b/src/pathspec.c
@@ -0,0 +1,151 @@
+/*
+ * 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 "pathspec.h"
+#include "attr_file.h"
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+char *git_pathspec_prefix(const git_strarray *pathspec)
+{
+ git_buf prefix = GIT_BUF_INIT;
+ const char *scan;
+
+ if (!pathspec || !pathspec->count ||
+ git_buf_common_prefix(&prefix, pathspec) < 0)
+ return NULL;
+
+ /* diff prefix will only be leading non-wildcards */
+ for (scan = prefix.ptr; *scan; ++scan) {
+ if (git__iswildcard(*scan) &&
+ (scan == prefix.ptr || (*(scan - 1) != '\\')))
+ break;
+ }
+ git_buf_truncate(&prefix, scan - prefix.ptr);
+
+ if (prefix.size <= 0) {
+ git_buf_free(&prefix);
+ return NULL;
+ }
+
+ git_buf_unescape(&prefix);
+
+ return git_buf_detach(&prefix);
+}
+
+/* is there anything in the spec that needs to be filtered on */
+bool git_pathspec_is_interesting(const git_strarray *pathspec)
+{
+ const char *str;
+
+ if (pathspec == NULL || pathspec->count == 0)
+ return false;
+ if (pathspec->count > 1)
+ return true;
+
+ str = pathspec->strings[0];
+ if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))
+ return false;
+ return true;
+}
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+int git_pathspec_init(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool)
+{
+ size_t i;
+
+ memset(vspec, 0, sizeof(*vspec));
+
+ if (!git_pathspec_is_interesting(strspec))
+ return 0;
+
+ if (git_vector_init(vspec, strspec->count, NULL) < 0)
+ return -1;
+
+ for (i = 0; i < strspec->count; ++i) {
+ int ret;
+ const char *pattern = strspec->strings[i];
+ git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
+ if (!match)
+ return -1;
+
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
+
+ ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
+ if (ret == GIT_ENOTFOUND) {
+ git__free(match);
+ continue;
+ } else if (ret < 0)
+ return ret;
+
+ if (git_vector_insert(vspec, match) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/* free data from the pathspec vector */
+void git_pathspec_free(git_vector *vspec)
+{
+ git_attr_fnmatch *match;
+ unsigned int i;
+
+ git_vector_foreach(vspec, i, match) {
+ git__free(match);
+ vspec->contents[i] = NULL;
+ }
+
+ git_vector_free(vspec);
+}
+
+/* match a path against the vectorized pathspec */
+bool git_pathspec_match_path(
+ git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold)
+{
+ unsigned int i;
+ git_attr_fnmatch *match;
+ int fnmatch_flags = 0;
+ int (*use_strcmp)(const char *, const char *);
+ int (*use_strncmp)(const char *, const char *, size_t);
+
+ if (!vspec || !vspec->length)
+ return true;
+
+ if (disable_fnmatch)
+ fnmatch_flags = -1;
+ else if (casefold)
+ fnmatch_flags = FNM_CASEFOLD;
+
+ if (casefold) {
+ use_strcmp = git__strcasecmp;
+ use_strncmp = git__strncasecmp;
+ } else {
+ use_strcmp = git__strcmp;
+ use_strncmp = git__strncmp;
+ }
+
+ git_vector_foreach(vspec, i, match) {
+ int result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
+
+ if (fnmatch_flags >= 0 && result == FNM_NOMATCH)
+ result = p_fnmatch(match->pattern, path, fnmatch_flags);
+
+ /* if we didn't match, look for exact dirname prefix match */
+ if (result == FNM_NOMATCH &&
+ (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 &&
+ use_strncmp(path, match->pattern, match->length) == 0 &&
+ path[match->length] == '/')
+ result = 0;
+
+ if (result == 0)
+ return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
+ }
+
+ return false;
+}
+
diff --git a/src/pathspec.h b/src/pathspec.h
new file mode 100644
index 000000000..31a1cdad9
--- /dev/null
+++ b/src/pathspec.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011-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_pathspec_h__
+#define INCLUDE_pathspec_h__
+
+#include "common.h"
+#include "buffer.h"
+#include "vector.h"
+#include "pool.h"
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+extern char *git_pathspec_prefix(const git_strarray *pathspec);
+
+/* is there anything in the spec that needs to be filtered on */
+extern bool git_pathspec_is_interesting(const git_strarray *pathspec);
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+extern int git_pathspec_init(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool);
+
+/* free data from the pathspec vector */
+extern void git_pathspec_free(git_vector *vspec);
+
+/* match a path against the vectorized pathspec */
+extern bool git_pathspec_match_path(
+ git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold);
+
+#endif
diff --git a/src/pkt.h b/src/pkt.h
deleted file mode 100644
index 0fdb5c7cd..000000000
--- a/src/pkt.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-
-#ifndef INCLUDE_pkt_h__
-#define INCLUDE_pkt_h__
-
-#include "common.h"
-#include "transport.h"
-#include "buffer.h"
-#include "posix.h"
-#include "git2/net.h"
-
-enum git_pkt_type {
- GIT_PKT_CMD,
- GIT_PKT_FLUSH,
- GIT_PKT_REF,
- GIT_PKT_HAVE,
- GIT_PKT_ACK,
- GIT_PKT_NAK,
- GIT_PKT_PACK,
- GIT_PKT_COMMENT,
- GIT_PKT_ERR,
- GIT_PKT_DATA,
- GIT_PKT_PROGRESS,
-};
-
-/* Used for multi-ack */
-enum git_ack_status {
- GIT_ACK_NONE,
- GIT_ACK_CONTINUE,
- GIT_ACK_COMMON,
- GIT_ACK_READY
-};
-
-/* This would be a flush pkt */
-typedef struct {
- enum git_pkt_type type;
-} git_pkt;
-
-struct git_pkt_cmd {
- enum git_pkt_type type;
- char *cmd;
- char *path;
- char *host;
-};
-
-/* This is a pkt-line with some info in it */
-typedef struct {
- enum git_pkt_type type;
- git_remote_head head;
- char *capabilities;
-} git_pkt_ref;
-
-/* Useful later */
-typedef struct {
- enum git_pkt_type type;
- git_oid oid;
- enum git_ack_status status;
-} git_pkt_ack;
-
-typedef struct {
- enum git_pkt_type type;
- char comment[GIT_FLEX_ARRAY];
-} git_pkt_comment;
-
-typedef struct {
- enum git_pkt_type type;
- int len;
- char data[GIT_FLEX_ARRAY];
-} git_pkt_data;
-
-typedef git_pkt_data git_pkt_progress;
-
-typedef struct {
- enum git_pkt_type type;
- char error[GIT_FLEX_ARRAY];
-} git_pkt_err;
-
-int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
-int git_pkt_buffer_flush(git_buf *buf);
-int git_pkt_send_flush(GIT_SOCKET s);
-int git_pkt_buffer_done(git_buf *buf);
-int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf);
-int git_pkt_buffer_have(git_oid *oid, git_buf *buf);
-void git_pkt_free(git_pkt *pkt);
-
-#endif
diff --git a/src/posix.c b/src/posix.c
index 985221dd5..d207ce1a0 100644
--- a/src/posix.c
+++ b/src/posix.c
@@ -205,3 +205,5 @@ int p_write(git_file fd, const void *buf, size_t cnt)
}
return 0;
}
+
+
diff --git a/src/protocol.c b/src/protocol.c
deleted file mode 100644
index affad5173..000000000
--- a/src/protocol.c
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-#include "common.h"
-#include "protocol.h"
-#include "pkt.h"
-#include "buffer.h"
-
-int git_protocol_store_refs(git_transport *t, int flushes)
-{
- gitno_buffer *buf = &t->buffer;
- git_vector *refs = &t->refs;
- int error, flush = 0, recvd;
- const char *line_end;
- git_pkt *pkt;
-
- do {
- if (buf->offset > 0)
- error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);
- else
- error = GIT_EBUFS;
-
- if (error < 0 && error != GIT_EBUFS)
- return -1;
-
- if (error == GIT_EBUFS) {
- if ((recvd = gitno_recv(buf)) < 0)
- return -1;
-
- if (recvd == 0 && !flush) {
- giterr_set(GITERR_NET, "Early EOF");
- return -1;
- }
-
- continue;
- }
-
- gitno_consume(buf, line_end);
- if (pkt->type == GIT_PKT_ERR) {
- giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);
- git__free(pkt);
- return -1;
- }
-
- if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0)
- return -1;
-
- if (pkt->type == GIT_PKT_FLUSH) {
- flush++;
- git_pkt_free(pkt);
- }
- } while (flush < flushes);
-
- return flush;
-}
-
-int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps)
-{
- const char *ptr;
-
- /* No refs or capabilites, odd but not a problem */
- if (pkt == NULL || pkt->capabilities == NULL)
- return 0;
-
- ptr = pkt->capabilities;
- while (ptr != NULL && *ptr != '\0') {
- if (*ptr == ' ')
- ptr++;
-
- if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
- caps->common = caps->ofs_delta = 1;
- ptr += strlen(GIT_CAP_OFS_DELTA);
- continue;
- }
-
- if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
- caps->common = caps->multi_ack = 1;
- ptr += strlen(GIT_CAP_MULTI_ACK);
- continue;
- }
-
- if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
- caps->common = caps->include_tag = 1;
- ptr += strlen(GIT_CAP_INCLUDE_TAG);
- continue;
- }
-
- /* Keep side-band check after side-band-64k */
- if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
- caps->common = caps->side_band_64k = 1;
- ptr += strlen(GIT_CAP_SIDE_BAND_64K);
- continue;
- }
-
- if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
- caps->common = caps->side_band = 1;
- ptr += strlen(GIT_CAP_SIDE_BAND);
- continue;
- }
-
-
- /* We don't know this capability, so skip it */
- ptr = strchr(ptr, ' ');
- }
-
- return 0;
-}
diff --git a/src/protocol.h b/src/protocol.h
deleted file mode 100644
index a990938e5..000000000
--- a/src/protocol.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-#ifndef INCLUDE_protocol_h__
-#define INCLUDE_protocol_h__
-
-#include "transport.h"
-#include "buffer.h"
-#include "pkt.h"
-
-int git_protocol_store_refs(git_transport *t, int flushes);
-int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps);
-
-#define GIT_SIDE_BAND_DATA 1
-#define GIT_SIDE_BAND_PROGRESS 2
-#define GIT_SIDE_BAND_ERROR 3
-
-#endif
diff --git a/src/reflog.c b/src/reflog.c
index a1ea7a27d..7b07c6a9f 100644
--- a/src/reflog.c
+++ b/src/reflog.c
@@ -166,6 +166,9 @@ void git_reflog_free(git_reflog *reflog)
unsigned int i;
git_reflog_entry *entry;
+ if (reflog == NULL)
+ return;
+
for (i=0; i < reflog->entries.length; i++) {
entry = git_vector_get(&reflog->entries, i);
@@ -185,7 +188,10 @@ static int retrieve_reflog_path(git_buf *path, git_reference *ref)
static int create_new_reflog_file(const char *filepath)
{
- int fd;
+ int fd, error;
+
+ if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0)
+ return error;
if ((fd = p_open(filepath,
O_WRONLY | O_CREAT | O_TRUNC,
@@ -285,8 +291,8 @@ success:
int git_reflog_append(git_reflog *reflog, const git_oid *new_oid,
const git_signature *committer, const char *msg)
{
- int count;
git_reflog_entry *entry;
+ const git_reflog_entry *previous;
const char *newline;
assert(reflog && new_oid && committer);
@@ -313,16 +319,12 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid,
}
}
- count = git_reflog_entrycount(reflog);
+ previous = git_reflog_entry_byindex(reflog, 0);
- if (count == 0)
+ if (previous == NULL)
git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO);
- else {
- const git_reflog_entry *previous;
-
- previous = git_reflog_entry_byindex(reflog, count -1);
+ else
git_oid_cpy(&entry->oid_old, &previous->oid_cur);
- }
git_oid_cpy(&entry->oid_cur, new_oid);
@@ -372,7 +374,7 @@ int git_reflog_rename(git_reference *ref, const char *new_name)
goto cleanup;
if (git_path_isdir(git_buf_cstr(&new_path)) &&
- (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0))
+ (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0))
goto cleanup;
if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0)
@@ -411,8 +413,16 @@ unsigned int git_reflog_entrycount(git_reflog *reflog)
const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx)
{
+ int pos;
+
assert(reflog);
- return git_vector_get(&reflog->entries, idx);
+
+ pos = git_reflog_entrycount(reflog) - (idx + 1);
+
+ if (pos < 0)
+ return NULL;
+
+ return git_vector_get(&reflog->entries, pos);
}
const git_oid * git_reflog_entry_oidold(const git_reflog_entry *entry)
@@ -441,7 +451,7 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry)
int git_reflog_drop(
git_reflog *reflog,
- unsigned int idx,
+ size_t idx,
int rewrite_previous_entry)
{
unsigned int entrycount;
@@ -451,31 +461,32 @@ int git_reflog_drop(
entrycount = git_reflog_entrycount(reflog);
- if (idx >= entrycount)
+ entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
+
+ if (entry == NULL)
return GIT_ENOTFOUND;
- entry = git_vector_get(&reflog->entries, idx);
reflog_entry_free(entry);
- if (git_vector_remove(&reflog->entries, idx) < 0)
+ if (git_vector_remove(&reflog->entries, entrycount - (idx + 1)) < 0)
return -1;
if (!rewrite_previous_entry)
return 0;
- /* No need to rewrite anything when removing the first entry */
+ /* No need to rewrite anything when removing the most recent entry */
if (idx == 0)
return 0;
- /* There are no more entries in the log */
+ /* Have the latest entry just been dropped? */
if (entrycount == 1)
return 0;
entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1);
- /* If the last entry has just been removed... */
+ /* If the oldest entry has just been removed... */
if (idx == entrycount - 1) {
- /* ...clear the oid_old member of the "new" last entry */
+ /* ...clear the oid_old member of the "new" oldest entry */
if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0)
return -1;
diff --git a/src/refs.c b/src/refs.c
index 833f2fcc8..97c97563e 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -123,7 +123,8 @@ static int reference_read(
if (git_buf_joinpath(&path, repo_path, ref_name) < 0)
return -1;
- result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated);
+ result = git_futils_readbuffer_updated(
+ file_content, path.ptr, mtime, NULL, updated);
git_buf_free(&path);
return result;
@@ -273,18 +274,15 @@ static int loose_write(git_reference *ref)
git_buf ref_path = GIT_BUF_INIT;
struct stat st;
- if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
- return -1;
-
/* Remove a possibly existing empty directory hierarchy
* which name would collide with the reference name
*/
- if (git_path_isdir(git_buf_cstr(&ref_path)) &&
- git_futils_rmdir_r(git_buf_cstr(&ref_path), NULL,
- GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0) {
- git_buf_free(&ref_path);
+ if (git_futils_rmdir_r(ref->name, ref->owner->path_repository,
+ GIT_RMDIR_SKIP_NONEMPTY) < 0)
+ return -1;
+
+ if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
return -1;
- }
if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
git_buf_free(&ref_path);
@@ -1948,10 +1946,10 @@ int git_reference_peel(
peel_error(error, ref, "Cannot retrieve reference target");
goto cleanup;
}
-
+
if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG)
error = git_object__dup(peeled, target);
- else
+ else
error = git_object_peel(peeled, target, target_type);
cleanup:
diff --git a/src/refs.h b/src/refs.h
index 54359f07b..a58bebd0d 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -28,10 +28,22 @@
#define GIT_PACKEDREFS_FILE_MODE 0666
#define GIT_HEAD_FILE "HEAD"
+#define GIT_ORIG_HEAD_FILE "ORIG_HEAD"
#define GIT_FETCH_HEAD_FILE "FETCH_HEAD"
#define GIT_MERGE_HEAD_FILE "MERGE_HEAD"
+#define GIT_REVERT_HEAD_FILE "REVERT_HEAD"
+#define GIT_CHERRY_PICK_HEAD_FILE "CHERRY_PICK_HEAD"
+#define GIT_BISECT_LOG_FILE "BISECT_LOG"
+#define GIT_REBASE_MERGE_DIR "rebase-merge/"
+#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive"
+#define GIT_REBASE_APPLY_DIR "rebase-apply/"
+#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing"
+#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying"
#define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master"
+#define GIT_STASH_FILE "stash"
+#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE
+
#define GIT_REFNAME_MAX 1024
struct git_reference {
diff --git a/src/refspec.c b/src/refspec.c
index b1790b32c..4d9915b7a 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -225,3 +225,21 @@ int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *n
return refspec_transform(out, spec->dst, spec->src, name);
}
+int git_refspec__serialize(git_buf *out, const git_refspec *refspec)
+{
+ if (refspec->force)
+ git_buf_putc(out, '+');
+
+ git_buf_printf(out, "%s:%s",
+ refspec->src != NULL ? refspec->src : "",
+ refspec->dst != NULL ? refspec->dst : "");
+
+ return git_buf_oom(out) == false;
+}
+
+int git_refspec_is_wildcard(const git_refspec *spec)
+{
+ assert(spec && spec->src);
+
+ return (spec->src[strlen(spec->src) - 1] == '*');
+}
diff --git a/src/refspec.h b/src/refspec.h
index 6e0596a55..e27314cc3 100644
--- a/src/refspec.h
+++ b/src/refspec.h
@@ -51,4 +51,14 @@ int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *n
*/
int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name);
+int git_refspec__serialize(git_buf *out, const git_refspec *refspec);
+
+/**
+ * Determines if a refspec is a wildcard refspec.
+ *
+ * @param spec the refspec
+ * @return 1 if the refspec is a wildcard, 0 otherwise
+ */
+int git_refspec_is_wildcard(const git_refspec *spec);
+
#endif
diff --git a/src/remote.c b/src/remote.c
index e05ea059f..4a4d160eb 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -14,7 +14,8 @@
#include "remote.h"
#include "fetch.h"
#include "refs.h"
-#include "pkt.h"
+#include "refspec.h"
+#include "fetchhead.h"
#include <regex.h>
@@ -69,6 +70,7 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con
memset(remote, 0x0, sizeof(git_remote));
remote->repo = repo;
remote->check_cert = 1;
+ remote->update_fetchhead = 1;
if (git_vector_init(&remote->refs, 32, NULL) < 0)
return -1;
@@ -117,6 +119,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
memset(remote, 0x0, sizeof(git_remote));
remote->check_cert = 1;
+ remote->update_fetchhead = 1;
remote->name = git__strdup(name);
GITERR_CHECK_ALLOC(remote->name);
@@ -132,6 +135,12 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0)
goto cleanup;
+
+ if (strlen(val) == 0) {
+ giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name);
+ error = -1;
+ goto cleanup;
+ }
remote->repo = repo;
remote->url = git__strdup(val);
@@ -144,8 +153,10 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name)
}
error = git_config_get_string(&val, config, git_buf_cstr(&buf));
- if (error == GIT_ENOTFOUND)
+ if (error == GIT_ENOTFOUND) {
+ val = NULL;
error = 0;
+ }
if (error < 0) {
error = -1;
@@ -201,12 +212,75 @@ cleanup:
return error;
}
+static int ensure_remote_name_is_valid(const char *name)
+{
+ git_buf buf = GIT_BUF_INIT;
+ git_refspec refspec;
+ int error = -1;
+
+ if (!name || *name == '\0')
+ goto cleanup;
+
+ git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", name);
+ error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true);
+
+ git_buf_free(&buf);
+ git_refspec__free(&refspec);
+
+cleanup:
+ if (error)
+ giterr_set(
+ GITERR_CONFIG,
+ "'%s' is not a valid remote name.", name);
+
+ return error;
+}
+
+static int update_config_refspec(
+ git_config *config,
+ const char *remote_name,
+ const git_refspec *refspec,
+ int git_direction)
+{
+ git_buf name = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ int error = -1;
+
+ if (refspec->src == NULL || refspec->dst == NULL)
+ return 0;
+
+ if (git_buf_printf(
+ &name,
+ "remote.%s.%s",
+ remote_name,
+ git_direction == GIT_DIR_FETCH ? "fetch" : "push") < 0)
+ goto cleanup;
+
+ if (git_refspec__serialize(&value, refspec) < 0)
+ goto cleanup;
+
+ error = git_config_set_string(
+ config,
+ git_buf_cstr(&name),
+ git_buf_cstr(&value));
+
+cleanup:
+ git_buf_free(&name);
+ git_buf_free(&value);
+
+ return error;
+}
+
int git_remote_save(const git_remote *remote)
{
int error;
git_config *config;
const char *tagopt = NULL;
- git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT;
+ git_buf buf = GIT_BUF_INIT;
+
+ assert(remote);
+
+ if (ensure_remote_name_is_valid(remote->name) < 0)
+ return -1;
if (git_repository_config__weakptr(&config, remote->repo) < 0)
return -1;
@@ -232,6 +306,7 @@ int git_remote_save(const git_remote *remote)
int error = git_config_delete(config, git_buf_cstr(&buf));
if (error == GIT_ENOTFOUND) {
error = 0;
+ giterr_clear();
}
if (error < 0) {
git_buf_free(&buf);
@@ -239,33 +314,19 @@ int git_remote_save(const git_remote *remote)
}
}
- if (remote->fetch.src != NULL && remote->fetch.dst != NULL) {
- git_buf_clear(&buf);
- git_buf_clear(&value);
- git_buf_printf(&buf, "remote.%s.fetch", remote->name);
- if (remote->fetch.force)
- git_buf_putc(&value, '+');
- git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
- return -1;
-
- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)
+ if (update_config_refspec(
+ config,
+ remote->name,
+ &remote->fetch,
+ GIT_DIR_FETCH) < 0)
goto on_error;
- }
-
- if (remote->push.src != NULL && remote->push.dst != NULL) {
- git_buf_clear(&buf);
- git_buf_clear(&value);
- git_buf_printf(&buf, "remote.%s.push", remote->name);
- if (remote->push.force)
- git_buf_putc(&value, '+');
- git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst);
- if (git_buf_oom(&buf) || git_buf_oom(&value))
- return -1;
- if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0)
+ if (update_config_refspec(
+ config,
+ remote->name,
+ &remote->push,
+ GIT_DIR_PUSH) < 0)
goto on_error;
- }
/*
* What action to take depends on the old and new values. This
@@ -300,13 +361,11 @@ int git_remote_save(const git_remote *remote)
}
git_buf_free(&buf);
- git_buf_free(&value);
return 0;
on_error:
git_buf_free(&buf);
- git_buf_free(&value);
return -1;
}
@@ -417,23 +476,30 @@ int git_remote_connect(git_remote *remote, int direction)
{
git_transport *t;
const char *url;
+ int flags = GIT_TRANSPORTFLAGS_NONE;
assert(remote);
+ t = remote->transport;
+
url = git_remote__urlfordirection(remote, direction);
if (url == NULL )
return -1;
- if (git_transport_new(&t, url) < 0)
+ /* A transport could have been supplied in advance with
+ * git_remote_set_transport */
+ if (!t && git_transport_new(&t, url) < 0)
return -1;
- t->progress_cb = remote->callbacks.progress;
- t->cb_data = remote->callbacks.data;
+ if (t->set_callbacks &&
+ t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.data) < 0)
+ goto on_error;
+
+ if (!remote->check_cert)
+ flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT;
- t->check_cert = remote->check_cert;
- if (t->connect(t, direction) < 0) {
+ if (t->connect(t, url, remote->cred_acquire_cb, direction, flags) < 0)
goto on_error;
- }
remote->transport = t;
@@ -446,42 +512,151 @@ on_error:
int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload)
{
- git_vector *refs = &remote->transport->refs;
- unsigned int i;
- git_pkt *p = NULL;
-
assert(remote);
- if (!remote->transport || !remote->transport->connected) {
+ if (!remote->transport) {
giterr_set(GITERR_NET, "The remote is not connected");
return -1;
}
- git_vector_foreach(refs, i, p) {
- git_pkt_ref *pkt = NULL;
+ return remote->transport->ls(remote->transport, list_cb, payload);
+}
- if (p->type != GIT_PKT_REF)
- continue;
+int git_remote_download(
+ git_remote *remote,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ int error;
- pkt = (git_pkt_ref *)p;
+ assert(remote);
- if (list_cb(&pkt->head, payload))
- return GIT_EUSER;
+ if ((error = git_fetch_negotiate(remote)) < 0)
+ return error;
+
+ return git_fetch_download_pack(remote, progress_cb, progress_payload);
+}
+
+static int update_tips_callback(git_remote_head *head, void *payload)
+{
+ git_vector *refs = (git_vector *)payload;
+ git_vector_insert(refs, head);
+
+ return 0;
+}
+
+static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src)
+{
+ unsigned int i;
+ git_remote_head *remote_ref;
+
+ assert(update_heads && fetchspec_src);
+
+ *out = NULL;
+
+ git_vector_foreach(update_heads, i, remote_ref) {
+ if (strcmp(remote_ref->name, fetchspec_src) == 0) {
+ *out = remote_ref;
+ break;
+ }
}
return 0;
}
-int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats)
+static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref)
{
- int error;
+ git_reference *resolved_ref = NULL;
+ git_reference *tracking_ref = NULL;
+ git_buf remote_name = GIT_BUF_INIT;
+ int error = 0;
- assert(remote && bytes && stats);
+ assert(out && remote && ref);
- if ((error = git_fetch_negotiate(remote)) < 0)
- return error;
+ *out = NULL;
+
+ if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 ||
+ (!git_reference_is_branch(resolved_ref)) ||
+ (error = git_branch_tracking(&tracking_ref, resolved_ref)) < 0 ||
+ (error = git_refspec_transform_l(&remote_name, &remote->fetch, git_reference_name(tracking_ref))) < 0) {
+ /* Not an error if HEAD is orphaned or no tracking branch */
+ if (error == GIT_ENOTFOUND)
+ error = 0;
+
+ goto cleanup;
+ }
+
+ error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name));
+
+cleanup:
+ git_reference_free(tracking_ref);
+ git_reference_free(resolved_ref);
+ git_buf_free(&remote_name);
+ return error;
+}
+
+static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_heads)
+{
+ struct git_refspec *spec;
+ git_reference *head_ref = NULL;
+ git_fetchhead_ref *fetchhead_ref;
+ git_remote_head *remote_ref, *merge_remote_ref;
+ git_vector fetchhead_refs;
+ bool include_all_fetchheads;
+ unsigned int i = 0;
+ int error = 0;
+
+ assert(remote);
+
+ spec = &remote->fetch;
+
+ if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0)
+ return -1;
+
+ /* Iff refspec is * (but not subdir slash star), include tags */
+ include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0);
+
+ /* Determine what to merge: if refspec was a wildcard, just use HEAD */
+ if (git_refspec_is_wildcard(spec)) {
+ if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 ||
+ (error = remote_head_for_ref(&merge_remote_ref, remote, update_heads, head_ref)) < 0)
+ goto cleanup;
+ } else {
+ /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */
+ if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0)
+ goto cleanup;
+ }
+
+ /* Create the FETCH_HEAD file */
+ git_vector_foreach(update_heads, i, remote_ref) {
+ int merge_this_fetchhead = (merge_remote_ref == remote_ref);
+
+ if (!include_all_fetchheads &&
+ !git_refspec_src_matches(spec, remote_ref->name) &&
+ !merge_this_fetchhead)
+ continue;
+
+ if (git_fetchhead_ref_create(&fetchhead_ref,
+ &remote_ref->oid,
+ merge_this_fetchhead,
+ remote_ref->name,
+ git_remote_url(remote)) < 0)
+ goto cleanup;
+
+ if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0)
+ goto cleanup;
+ }
+
+ git_fetchhead_write(remote->repo, &fetchhead_refs);
+
+cleanup:
+ for (i = 0; i < fetchhead_refs.length; ++i)
+ git_fetchhead_ref_free(fetchhead_refs.contents[i]);
+
+ git_vector_free(&fetchhead_refs);
+ git_reference_free(head_ref);
- return git_fetch_download_pack(remote, bytes, stats);
+ return error;
}
int git_remote_update_tips(git_remote *remote)
@@ -490,48 +665,48 @@ int git_remote_update_tips(git_remote *remote)
unsigned int i = 0;
git_buf refname = GIT_BUF_INIT;
git_oid old;
- git_pkt *pkt;
git_odb *odb;
- git_vector *refs;
git_remote_head *head;
git_reference *ref;
struct git_refspec *spec;
git_refspec tagspec;
+ git_vector refs, update_heads;
assert(remote);
- refs = &remote->transport->refs;
spec = &remote->fetch;
-
- if (refs->length == 0)
- return 0;
-
+
if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
return -1;
if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0)
return -1;
- /* HEAD is only allowed to be the first in the list */
- pkt = refs->contents[0];
- head = &((git_pkt_ref *)pkt)->head;
- if (!strcmp(head->name, GIT_HEAD_FILE)) {
- if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
- return -1;
+ /* Make a copy of the transport's refs */
+ if (git_vector_init(&refs, 16, NULL) < 0 ||
+ git_vector_init(&update_heads, 16, NULL) < 0)
+ return -1;
- i = 1;
- git_reference_free(ref);
+ if (remote->transport->ls(remote->transport, update_tips_callback, &refs) < 0)
+ goto on_error;
+
+ /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */
+ if (refs.length > 0) {
+ head = (git_remote_head *)refs.contents[0];
+
+ if (!strcmp(head->name, GIT_HEAD_FILE)) {
+ if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0)
+ goto on_error;
+
+ i = 1;
+ git_reference_free(ref);
+ }
}
- for (; i < refs->length; ++i) {
- git_pkt *pkt = refs->contents[i];
+ for (; i < refs.length; ++i) {
+ head = (git_remote_head *)refs.contents[i];
autotag = 0;
- if (pkt->type == GIT_PKT_REF)
- head = &((git_pkt_ref *)pkt)->head;
- else
- continue;
-
/* Ignore malformed ref names (which also saves us from tag^{} */
if (!git_reference_is_valid_name(head->name))
continue;
@@ -557,6 +732,9 @@ int git_remote_update_tips(git_remote *remote)
if (autotag && !git_odb_exists(odb, &head->oid))
continue;
+ if (git_vector_insert(&update_heads, head) < 0)
+ goto on_error;
+
error = git_reference_name_to_oid(&old, remote->repo, refname.ptr);
if (error < 0 && error != GIT_ENOTFOUND)
goto on_error;
@@ -580,11 +758,19 @@ int git_remote_update_tips(git_remote *remote)
}
}
+ if (git_remote_update_fetchhead(remote) &&
+ (error = git_remote_write_fetchhead(remote, &update_heads)) < 0)
+ goto on_error;
+
+ git_vector_free(&refs);
+ git_vector_free(&update_heads);
git_refspec__free(&tagspec);
git_buf_free(&refname);
return 0;
on_error:
+ git_vector_free(&refs);
+ git_vector_free(&update_heads);
git_refspec__free(&tagspec);
git_buf_free(&refname);
return -1;
@@ -593,21 +779,31 @@ on_error:
int git_remote_connected(git_remote *remote)
{
+ int connected;
+
assert(remote);
- return remote->transport == NULL ? 0 : remote->transport->connected;
+
+ 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;
}
void git_remote_stop(git_remote *remote)
{
- git_atomic_set(&remote->transport->cancel, 1);
+ if (remote->transport->cancel)
+ remote->transport->cancel(remote->transport);
}
void git_remote_disconnect(git_remote *remote)
{
assert(remote);
- if (remote->transport != NULL && remote->transport->connected)
- remote->transport->close(remote->transport);
+ if (git_remote_connected(remote))
+ remote->transport->close(remote->transport);
}
void git_remote_free(git_remote *remote)
@@ -736,10 +932,39 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback
memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks));
+ if (remote->transport && remote->transport->set_callbacks)
+ remote->transport->set_callbacks(remote->transport,
+ remote->callbacks.progress,
+ NULL,
+ remote->callbacks.data);
+}
+
+void git_remote_set_cred_acquire_cb(
+ git_remote *remote,
+ git_cred_acquire_cb cred_acquire_cb)
+{
+ assert(remote);
+
+ remote->cred_acquire_cb = cred_acquire_cb;
+}
+
+int git_remote_set_transport(git_remote *remote, git_transport *transport)
+{
+ assert(remote && transport);
+
if (remote->transport) {
- remote->transport->progress_cb = remote->callbacks.progress;
- remote->transport->cb_data = remote->callbacks.data;
+ giterr_set(GITERR_NET, "A transport is already bound to this remote");
+ return -1;
}
+
+ remote->transport = transport;
+ return 0;
+}
+
+const git_transfer_progress* git_remote_stats(git_remote *remote)
+{
+ assert(remote);
+ return &remote->stats;
}
int git_remote_autotag(git_remote *remote)
@@ -751,3 +976,298 @@ void git_remote_set_autotag(git_remote *remote, int value)
{
remote->download_tags = value;
}
+
+static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
+{
+ int error;
+ git_remote *remote;
+
+ error = git_remote_load(&remote, repo, name);
+
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ if (error < 0)
+ return error;
+
+ git_remote_free(remote);
+
+ giterr_set(
+ GITERR_CONFIG,
+ "Remote '%s' already exists.", name);
+
+ return GIT_EEXISTS;
+}
+
+static int rename_remote_config_section(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ git_buf old_section_name = GIT_BUF_INIT,
+ new_section_name = GIT_BUF_INIT;
+ int error = -1;
+
+ if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0)
+ goto cleanup;
+
+ error = git_config_rename_section(
+ repo,
+ git_buf_cstr(&old_section_name),
+ git_buf_cstr(&new_section_name));
+
+cleanup:
+ git_buf_free(&old_section_name);
+ git_buf_free(&new_section_name);
+
+ return error;
+}
+
+struct update_data
+{
+ git_config *config;
+ const char *old_remote_name;
+ const char *new_remote_name;
+};
+
+static int update_config_entries_cb(
+ const git_config_entry *entry,
+ void *payload)
+{
+ struct update_data *data = (struct update_data *)payload;
+
+ if (strcmp(entry->value, data->old_remote_name))
+ return 0;
+
+ return git_config_set_string(
+ data->config,
+ entry->name,
+ data->new_remote_name);
+}
+
+static int update_branch_remote_config_entry(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ git_config *config;
+ struct update_data data;
+
+ if (git_repository_config__weakptr(&config, repo) < 0)
+ return -1;
+
+ data.config = config;
+ data.old_remote_name = old_name;
+ data.new_remote_name = new_name;
+
+ return git_config_foreach_match(
+ config,
+ "branch\\..+\\.remote",
+ update_config_entries_cb, &data);
+}
+
+static int rename_cb(const char *ref, void *data)
+{
+ if (git__prefixcmp(ref, GIT_REFS_REMOTES_DIR))
+ return 0;
+
+ return git_vector_insert((git_vector *)data, git__strdup(ref));
+}
+
+static int rename_one_remote_reference(
+ git_repository *repo,
+ const char *reference_name,
+ const char *old_remote_name,
+ const char *new_remote_name)
+{
+ int error = -1;
+ git_buf new_name = GIT_BUF_INIT;
+ git_reference *reference = NULL;
+
+ if (git_buf_printf(
+ &new_name,
+ GIT_REFS_REMOTES_DIR "%s%s",
+ new_remote_name,
+ reference_name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0)
+ return -1;
+
+ if (git_reference_lookup(&reference, repo, reference_name) < 0)
+ goto cleanup;
+
+ error = git_reference_rename(reference, git_buf_cstr(&new_name), 0);
+
+cleanup:
+ git_reference_free(reference);
+ git_buf_free(&new_name);
+ return error;
+}
+
+static int rename_remote_references(
+ git_repository *repo,
+ const char *old_name,
+ const char *new_name)
+{
+ git_vector refnames;
+ int error = -1;
+ unsigned int i;
+ char *name;
+
+ if (git_vector_init(&refnames, 8, NULL) < 0)
+ goto cleanup;
+
+ if (git_reference_foreach(
+ repo,
+ GIT_REF_LISTALL,
+ rename_cb,
+ &refnames) < 0)
+ goto cleanup;
+
+ git_vector_foreach(&refnames, i, name) {
+ if ((error = rename_one_remote_reference(repo, name, old_name, new_name)) < 0)
+ goto cleanup;
+ }
+
+ error = 0;
+cleanup:
+ git_vector_foreach(&refnames, i, name) {
+ git__free(name);
+ }
+
+ git_vector_free(&refnames);
+ return error;
+}
+
+static int rename_fetch_refspecs(
+ git_remote *remote,
+ const char *new_name,
+ int (*callback)(const char *problematic_refspec, void *payload),
+ void *payload)
+{
+ git_config *config;
+ const git_refspec *fetch_refspec;
+ git_buf dst_prefix = GIT_BUF_INIT, serialized = GIT_BUF_INIT;
+ const char* pos;
+ int error = -1;
+
+ fetch_refspec = git_remote_fetchspec(remote);
+
+ /* Is there a refspec to deal with? */
+ if (fetch_refspec->src == NULL &&
+ fetch_refspec->dst == NULL)
+ return 0;
+
+ if (git_refspec__serialize(&serialized, fetch_refspec) < 0)
+ goto cleanup;
+
+ /* Is it an in-memory remote? */
+ if (remote->name == '\0') {
+ error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0;
+ goto cleanup;
+ }
+
+ if (git_buf_printf(&dst_prefix, ":refs/remotes/%s/", remote->name) < 0)
+ goto cleanup;
+
+ pos = strstr(git_buf_cstr(&serialized), git_buf_cstr(&dst_prefix));
+
+ /* Does the dst part of the refspec follow the extected standard format? */
+ if (!pos) {
+ error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0;
+ goto cleanup;
+ }
+
+ if (git_buf_splice(
+ &serialized,
+ pos - git_buf_cstr(&serialized) + strlen(":refs/remotes/"),
+ strlen(remote->name), new_name,
+ strlen(new_name)) < 0)
+ goto cleanup;
+
+ git_refspec__free(&remote->fetch);
+
+ if (git_refspec__parse(&remote->fetch, git_buf_cstr(&serialized), true) < 0)
+ goto cleanup;
+
+ if (git_repository_config__weakptr(&config, remote->repo) < 0)
+ goto cleanup;
+
+ error = update_config_refspec(config, new_name, &remote->fetch, GIT_DIR_FETCH);
+
+cleanup:
+ git_buf_free(&serialized);
+ git_buf_free(&dst_prefix);
+ return error;
+}
+
+int git_remote_rename(
+ git_remote *remote,
+ const char *new_name,
+ int (*callback)(const char *problematic_refspec, void *payload),
+ void *payload)
+{
+ int error;
+
+ assert(remote && new_name);
+
+ if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0)
+ return error;
+
+ if ((error = ensure_remote_name_is_valid(new_name)) < 0)
+ return error;
+
+ if (!remote->name) {
+ if ((error = rename_fetch_refspecs(
+ remote,
+ new_name,
+ callback,
+ payload)) < 0)
+ return error;
+
+ remote->name = git__strdup(new_name);
+
+ return git_remote_save(remote);
+ }
+
+ if ((error = rename_remote_config_section(
+ remote->repo,
+ remote->name,
+ new_name)) < 0)
+ return error;
+
+ if ((error = update_branch_remote_config_entry(
+ remote->repo,
+ remote->name,
+ new_name)) < 0)
+ return error;
+
+ if ((error = rename_remote_references(
+ remote->repo,
+ remote->name,
+ new_name)) < 0)
+ return error;
+
+ if ((error = rename_fetch_refspecs(
+ remote,
+ new_name,
+ callback,
+ payload)) < 0)
+ return error;
+
+ git__free(remote->name);
+ remote->name = git__strdup(new_name);
+
+ return 0;
+}
+
+int git_remote_update_fetchhead(git_remote *remote)
+{
+ return remote->update_fetchhead;
+}
+
+void git_remote_set_update_fetchhead(git_remote *remote, int value)
+{
+ remote->update_fetchhead = value;
+}
diff --git a/src/remote.h b/src/remote.h
index 05073db8c..840c9a905 100644
--- a/src/remote.h
+++ b/src/remote.h
@@ -8,9 +8,9 @@
#define INCLUDE_remote_h__
#include "git2/remote.h"
+#include "git2/transport.h"
#include "refspec.h"
-#include "transport.h"
#include "repository.h"
#define GIT_REMOTE_ORIGIN "origin"
@@ -22,12 +22,15 @@ struct git_remote {
git_vector refs;
struct git_refspec fetch;
struct git_refspec push;
+ git_cred_acquire_cb cred_acquire_cb;
git_transport *transport;
git_repository *repo;
git_remote_callbacks callbacks;
+ git_transfer_progress stats;
unsigned int need_pack:1,
download_tags:2, /* There are four possible values */
- check_cert:1;
+ check_cert:1,
+ update_fetchhead:1;
};
const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
diff --git a/src/repository.c b/src/repository.c
index 43e0eda8f..f82dc108b 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -20,6 +20,7 @@
#include "filter.h"
#include "odb.h"
#include "remote.h"
+#include "merge.h"
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
@@ -449,37 +450,46 @@ static int load_config(
const char *xdg_config_path,
const char *system_config_path)
{
+ int error;
git_buf config_path = GIT_BUF_INIT;
git_config *cfg = NULL;
assert(repo && out);
- if (git_config_new(&cfg) < 0)
- return -1;
+ if ((error = git_config_new(&cfg)) < 0)
+ return error;
- if (git_buf_joinpath(
- &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0)
+ error = git_buf_joinpath(
+ &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO);
+ if (error < 0)
goto on_error;
- if (git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0) < 0)
+ if ((error = git_config_add_file_ondisk(
+ cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
goto on_error;
git_buf_free(&config_path);
- if (global_config_path != NULL) {
- if (git_config_add_file_ondisk(cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0) < 0)
- goto on_error;
- }
+ if (global_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
- if (xdg_config_path != NULL) {
- if (git_config_add_file_ondisk(cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0) < 0)
- goto on_error;
- }
+ if (xdg_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
- if (system_config_path != NULL) {
- if (git_config_add_file_ondisk(cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0) < 0)
- goto on_error;
- }
+ if (system_config_path != NULL &&
+ (error = git_config_add_file_ondisk(
+ cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0)) < 0 &&
+ error != GIT_ENOTFOUND)
+ goto on_error;
+
+ giterr_clear(); /* clear any lingering ENOTFOUND errors */
*out = cfg;
return 0;
@@ -488,7 +498,7 @@ on_error:
git_buf_free(&config_path);
git_config_free(cfg);
*out = NULL;
- return -1;
+ return error;
}
int git_repository_config__weakptr(git_config **out, git_repository *repo)
@@ -749,6 +759,23 @@ static bool are_symlinks_supported(const char *wd_path)
return _symlinks_supported;
}
+static int create_empty_file(const char *path, mode_t mode)
+{
+ int fd;
+
+ if ((fd = p_creat(path, mode)) < 0) {
+ giterr_set(GITERR_OS, "Error while creating '%s'", path);
+ return -1;
+ }
+
+ if (p_close(fd) < 0) {
+ giterr_set(GITERR_OS, "Error while closing '%s'", path);
+ return -1;
+ }
+
+ return 0;
+}
+
static int repo_init_config(
const char *repo_dir,
const char *work_dir,
@@ -765,6 +792,12 @@ static int repo_init_config(
if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0)
return -1;
+ if (!git_path_isfile(git_buf_cstr(&cfg_path)) &&
+ create_empty_file(git_buf_cstr(&cfg_path), GIT_CONFIG_FILE_MODE) < 0) {
+ git_buf_free(&cfg_path);
+ return -1;
+ }
+
if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) {
git_buf_free(&cfg_path);
return -1;
@@ -1206,9 +1239,19 @@ int git_repository_head_detached(git_repository *repo)
int git_repository_head(git_reference **head_out, git_repository *repo)
{
+ git_reference *head;
int error;
- error = git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1);
+ if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
+ return error;
+
+ if (git_reference_type(head) == GIT_REF_OID) {
+ *head_out = head;
+ return 0;
+ }
+
+ error = git_reference_lookup_resolved(head_out, repo, git_reference_target(head), -1);
+ git_reference_free(head);
return error == GIT_ENOTFOUND ? GIT_EORPHANEDHEAD : error;
}
@@ -1230,36 +1273,47 @@ int git_repository_head_orphan(git_repository *repo)
return 0;
}
+static int at_least_one_cb(const char *refname, void *payload)
+{
+ GIT_UNUSED(refname);
+ GIT_UNUSED(payload);
+
+ return GIT_EUSER;
+}
+
+static int repo_contains_no_reference(git_repository *repo)
+{
+ int error;
+
+ error = git_reference_foreach(repo, GIT_REF_LISTALL, at_least_one_cb, NULL);
+
+ if (error == GIT_EUSER)
+ return 0;
+
+ return error == 0 ? 1 : error;
+}
+
int git_repository_is_empty(git_repository *repo)
{
- git_reference *head = NULL, *branch = NULL;
+ git_reference *head = NULL;
int error;
if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0)
return -1;
- if (git_reference_type(head) != GIT_REF_SYMBOLIC) {
- git_reference_free(head);
- return 0;
- }
+ if (!(error = git_reference_type(head) == GIT_REF_SYMBOLIC))
+ goto cleanup;
- if (strcmp(git_reference_target(head), GIT_REFS_HEADS_DIR "master") != 0) {
- git_reference_free(head);
- return 0;
- }
+ if (!(error = strcmp(
+ git_reference_target(head),
+ GIT_REFS_HEADS_DIR "master") == 0))
+ goto cleanup;
- error = git_reference_resolve(&branch, head);
+ error = repo_contains_no_reference(repo);
+cleanup:
git_reference_free(head);
- git_reference_free(branch);
-
- if (error == GIT_ENOTFOUND)
- return 1;
-
- if (error < 0)
- return -1;
-
- return 0;
+ return error < 0 ? -1 : error;
}
const char *git_repository_path(git_repository *repo)
@@ -1348,15 +1402,13 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo)
return 0;
}
-#define MERGE_MSG_FILE "MERGE_MSG"
-
int git_repository_message(char *buffer, size_t len, git_repository *repo)
{
git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
struct stat st;
int error;
- if (git_buf_joinpath(&path, repo->path_repository, MERGE_MSG_FILE) < 0)
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
return -1;
if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) {
@@ -1382,7 +1434,7 @@ int git_repository_message_remove(git_repository *repo)
git_buf path = GIT_BUF_INIT;
int error;
- if (git_buf_joinpath(&path, repo->path_repository, MERGE_MSG_FILE) < 0)
+ if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0)
return -1;
error = p_unlink(git_buf_cstr(&path));
@@ -1541,3 +1593,40 @@ cleanup:
git_reference_free(new_head);
return error;
}
+
+/**
+ * Loosely ported from git.git
+ * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289
+ */
+int git_repository_state(git_repository *repo)
+{
+ git_buf repo_path = GIT_BUF_INIT;
+ int state = GIT_REPOSITORY_STATE_NONE;
+
+ assert(repo);
+
+ if (git_buf_puts(&repo_path, repo->path_repository) < 0)
+ return -1;
+
+ if (git_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE))
+ state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE;
+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR))
+ state = GIT_REPOSITORY_STATE_REBASE_MERGE;
+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE))
+ state = GIT_REPOSITORY_STATE_REBASE;
+ else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE))
+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX;
+ else if (git_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR))
+ state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE;
+ else if (git_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_MERGE;
+ else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_REVERT;
+ else if(git_path_contains_file(&repo_path, GIT_CHERRY_PICK_HEAD_FILE))
+ state = GIT_REPOSITORY_STATE_CHERRY_PICK;
+ else if(git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE))
+ state = GIT_REPOSITORY_STATE_BISECT;
+
+ git_buf_free(&repo_path);
+ return state;
+}
diff --git a/src/reset.c b/src/reset.c
index 560ae17b1..8f470b26a 100644
--- a/src/reset.c
+++ b/src/reset.c
@@ -8,8 +8,10 @@
#include "common.h"
#include "commit.h"
#include "tag.h"
+#include "merge.h"
#include "git2/reset.h"
#include "git2/checkout.h"
+#include "git2/merge.h"
#define ERROR_MSG "Cannot perform reset"
@@ -88,6 +90,12 @@ int git_reset(
goto cleanup;
}
+ if (reset_type == GIT_RESET_SOFT && (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE)) {
+ giterr_set(GITERR_OBJECT, "%s (soft) while in the middle of a merge.", ERROR_MSG);
+ error = GIT_EUNMERGED;
+ goto cleanup;
+ }
+
//TODO: Check for unmerged entries
if (update_head(repo, commit) < 0)
@@ -108,7 +116,7 @@ int git_reset(
goto cleanup;
}
- if (git_index_read_tree(index, tree, NULL) < 0) {
+ if (git_index_read_tree(index, tree) < 0) {
giterr_set(GITERR_INDEX, "%s - Failed to update the index.", ERROR_MSG);
goto cleanup;
}
@@ -118,18 +126,20 @@ int git_reset(
goto cleanup;
}
+ if ((error = git_merge__cleanup(repo)) < 0) {
+ giterr_set(GITERR_INDEX, "%s - Failed to clean up merge data.", ERROR_MSG);
+ goto cleanup;
+ }
+
if (reset_type == GIT_RESET_MIXED) {
error = 0;
goto cleanup;
}
memset(&opts, 0, sizeof(opts));
- opts.checkout_strategy =
- GIT_CHECKOUT_CREATE_MISSING
- | GIT_CHECKOUT_OVERWRITE_MODIFIED
- | GIT_CHECKOUT_REMOVE_UNTRACKED;
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
- if (git_checkout_index(repo, &opts, NULL) < 0) {
+ if (git_checkout_index(repo, NULL, &opts) < 0) {
giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG);
goto cleanup;
}
diff --git a/src/revparse.c b/src/revparse.c
index 83eea7d3f..6b49402c4 100644
--- a/src/revparse.c
+++ b/src/revparse.c
@@ -201,7 +201,7 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out,
numentries = git_reflog_entrycount(reflog);
- for (i = numentries - 1; i >= 0; i--) {
+ for (i = 0; i < numentries; i++) {
entry = git_reflog_entry_byindex(reflog, i);
msg = git_reflog_entry_msg(entry);
@@ -263,15 +263,15 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, unsigned i
}
entry = git_reflog_entry_byindex(reflog, identifier);
- git_oid_cpy(oid, git_reflog_entry_oidold(entry));
+ git_oid_cpy(oid, git_reflog_entry_oidnew(entry));
error = 0;
goto cleanup;
} else {
- int i;
+ unsigned int i;
git_time commit_time;
- for (i = numentries - 1; i >= 0; i--) {
+ for (i = 0; i < numentries; i++) {
entry = git_reflog_entry_byindex(reflog, i);
commit_time = git_reflog_entry_committer(entry)->when;
diff --git a/src/sha1.h b/src/sha1.h
deleted file mode 100644
index 41e8abad6..000000000
--- a/src/sha1.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-
-#ifndef INCLUDE_sha1_h__
-#define INCLUDE_sha1_h__
-
-#ifdef OPENSSL_SHA
-# include <openssl/sha.h>
-
-#else
-typedef struct {
- unsigned long long size;
- unsigned int H[5];
- unsigned int W[16];
-} blk_SHA_CTX;
-
-
-void git__blk_SHA1_Init(blk_SHA_CTX *ctx);
-void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, size_t len);
-void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx);
-
-#define SHA_CTX blk_SHA_CTX
-#define SHA1_Init git__blk_SHA1_Init
-#define SHA1_Update git__blk_SHA1_Update
-#define SHA1_Final git__blk_SHA1_Final
-
-#endif // OPENSSL_SHA
-
-#endif
diff --git a/src/stash.c b/src/stash.c
new file mode 100644
index 000000000..b74429aca
--- /dev/null
+++ b/src/stash.c
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "common.h"
+#include "repository.h"
+#include "commit.h"
+#include "tree.h"
+#include "reflog.h"
+#include "git2/diff.h"
+#include "git2/stash.h"
+#include "git2/status.h"
+#include "git2/checkout.h"
+
+static int create_error(int error, const char *msg)
+{
+ giterr_set(GITERR_STASH, "Cannot stash changes - %s", msg);
+ return error;
+}
+
+static int ensure_non_bare_repository(git_repository *repo)
+{
+ if (!git_repository_is_bare(repo))
+ return 0;
+
+ return create_error(GIT_EBAREREPO,
+ "Stash related operations require a working directory.");
+}
+
+static int retrieve_head(git_reference **out, git_repository *repo)
+{
+ int error = git_repository_head(out, repo);
+
+ if (error == GIT_EORPHANEDHEAD)
+ return create_error(error, "You do not have the initial commit yet.");
+
+ return error;
+}
+
+static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit)
+{
+ char *formatted_oid;
+
+ formatted_oid = git_oid_allocfmt(b_commit);
+ GITERR_CHECK_ALLOC(formatted_oid);
+
+ git_buf_put(out, formatted_oid, 7);
+ git__free(formatted_oid);
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int append_commit_description(git_buf *out, git_commit* commit)
+{
+ const char *message;
+ int pos = 0, len;
+
+ if (append_abbreviated_oid(out, git_commit_id(commit)) < 0)
+ return -1;
+
+ message = git_commit_message(commit);
+ len = strlen(message);
+
+ /* TODO: Replace with proper commit short message
+ * when git_commit_message_short() is implemented.
+ */
+ while (pos < len && message[pos] != '\n')
+ pos++;
+
+ git_buf_putc(out, ' ');
+ git_buf_put(out, message, pos);
+ git_buf_putc(out, '\n');
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int retrieve_base_commit_and_message(
+ git_commit **b_commit,
+ git_buf *stash_message,
+ git_repository *repo)
+{
+ git_reference *head = NULL;
+ int error;
+
+ if ((error = retrieve_head(&head, repo)) < 0)
+ return error;
+
+ error = -1;
+
+ if (strcmp("HEAD", git_reference_name(head)) == 0)
+ git_buf_puts(stash_message, "(no branch): ");
+ else
+ git_buf_printf(
+ stash_message,
+ "%s: ",
+ git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR));
+
+ if (git_commit_lookup(b_commit, repo, git_reference_oid(head)) < 0)
+ goto cleanup;
+
+ if (append_commit_description(stash_message, *b_commit) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_reference_free(head);
+ return error;
+}
+
+static int build_tree_from_index(git_tree **out, git_index *index)
+{
+ git_oid i_tree_oid;
+
+ if (git_index_write_tree(&i_tree_oid, index) < 0)
+ return -1;
+
+ return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);
+}
+
+static int commit_index(
+ git_commit **i_commit,
+ git_index *index,
+ git_signature *stasher,
+ const char *message,
+ const git_commit *parent)
+{
+ git_tree *i_tree = NULL;
+ git_oid i_commit_oid;
+ git_buf msg = GIT_BUF_INIT;
+ int error = -1;
+
+ if (build_tree_from_index(&i_tree, index) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&msg, "index on %s\n", message) < 0)
+ goto cleanup;
+
+ if (git_commit_create(
+ &i_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ git_buf_cstr(&msg),
+ i_tree,
+ 1,
+ &parent) < 0)
+ goto cleanup;
+
+ error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid);
+
+cleanup:
+ git_tree_free(i_tree);
+ git_buf_free(&msg);
+ return error;
+}
+
+struct cb_data {
+ git_index *index;
+
+ bool include_changed;
+ bool include_untracked;
+ bool include_ignored;
+};
+
+static int update_index_cb(
+ void *cb_data,
+ const git_diff_delta *delta,
+ float progress)
+{
+ int pos;
+ struct cb_data *data = (struct cb_data *)cb_data;
+
+ GIT_UNUSED(progress);
+
+ switch (delta->status) {
+ case GIT_DELTA_IGNORED:
+ if (!data->include_ignored)
+ break;
+
+ return git_index_add_from_workdir(data->index, delta->new_file.path);
+
+ case GIT_DELTA_UNTRACKED:
+ if (!data->include_untracked)
+ break;
+
+ return git_index_add_from_workdir(data->index, delta->new_file.path);
+
+ case GIT_DELTA_ADDED:
+ /* Fall through */
+ case GIT_DELTA_MODIFIED:
+ if (!data->include_changed)
+ break;
+
+ return git_index_add_from_workdir(data->index, delta->new_file.path);
+
+ case GIT_DELTA_DELETED:
+ if (!data->include_changed)
+ break;
+
+ if ((pos = git_index_find(data->index, delta->new_file.path)) < 0)
+ return -1;
+
+ if (git_index_remove(data->index, delta->new_file.path, 0) < 0)
+ return -1;
+
+ default:
+ /* Unimplemented */
+ giterr_set(
+ GITERR_INVALID,
+ "Cannot update index. Unimplemented status kind (%d)",
+ delta->status);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int build_untracked_tree(
+ git_tree **tree_out,
+ git_index *index,
+ git_commit *i_commit,
+ uint32_t flags)
+{
+ git_tree *i_tree = NULL;
+ git_diff_list *diff = NULL;
+ git_diff_options opts = {0};
+ struct cb_data data = {0};
+ int error = -1;
+
+ git_index_clear(index);
+
+ data.index = index;
+
+ if (flags & GIT_STASH_INCLUDE_UNTRACKED) {
+ opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ data.include_untracked = true;
+ }
+
+ if (flags & GIT_STASH_INCLUDE_IGNORED) {
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
+ data.include_ignored = true;
+ }
+
+ if (git_commit_tree(&i_tree, i_commit) < 0)
+ goto cleanup;
+
+ if (git_diff_workdir_to_tree(&diff, git_index_owner(index), i_tree, &opts) < 0)
+ goto cleanup;
+
+ if (git_diff_foreach(diff, &data, update_index_cb, NULL, NULL) < 0)
+ goto cleanup;
+
+ if (build_tree_from_index(tree_out, index) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_diff_list_free(diff);
+ git_tree_free(i_tree);
+ return error;
+}
+
+static int commit_untracked(
+ git_commit **u_commit,
+ git_index *index,
+ git_signature *stasher,
+ const char *message,
+ git_commit *i_commit,
+ uint32_t flags)
+{
+ git_tree *u_tree = NULL;
+ git_oid u_commit_oid;
+ git_buf msg = GIT_BUF_INIT;
+ int error = -1;
+
+ if (build_untracked_tree(&u_tree, index, i_commit, flags) < 0)
+ goto cleanup;
+
+ if (git_buf_printf(&msg, "untracked files on %s\n", message) < 0)
+ goto cleanup;
+
+ if (git_commit_create(
+ &u_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ git_buf_cstr(&msg),
+ u_tree,
+ 0,
+ NULL) < 0)
+ goto cleanup;
+
+ error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid);
+
+cleanup:
+ git_tree_free(u_tree);
+ git_buf_free(&msg);
+ return error;
+}
+
+static int build_workdir_tree(
+ git_tree **tree_out,
+ git_index *index,
+ git_commit *b_commit)
+{
+ git_repository *repo = git_index_owner(index);
+ git_tree *b_tree = NULL;
+ git_diff_list *diff = NULL, *diff2 = NULL;
+ git_diff_options opts = {0};
+ struct cb_data data = {0};
+ int error = -1;
+
+ if (git_commit_tree(&b_tree, b_commit) < 0)
+ goto cleanup;
+
+ if (git_diff_index_to_tree(&diff, repo, b_tree, NULL, &opts) < 0)
+ goto cleanup;
+
+ if (git_diff_workdir_to_index(&diff2, repo, NULL, &opts) < 0)
+ goto cleanup;
+
+ if (git_diff_merge(diff, diff2) < 0)
+ goto cleanup;
+
+ data.index = index;
+ data.include_changed = true;
+
+ if (git_diff_foreach(diff, &data, update_index_cb, NULL, NULL) < 0)
+ goto cleanup;
+
+ if (build_tree_from_index(tree_out, index) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_diff_list_free(diff);
+ git_diff_list_free(diff2);
+ git_tree_free(b_tree);
+ return error;
+}
+
+static int commit_worktree(
+ git_oid *w_commit_oid,
+ git_index *index,
+ git_signature *stasher,
+ const char *message,
+ git_commit *i_commit,
+ git_commit *b_commit,
+ git_commit *u_commit)
+{
+ git_tree *w_tree = NULL, *i_tree = NULL;
+ int error = -1;
+
+ const git_commit *parents[] = { NULL, NULL, NULL };
+
+ parents[0] = b_commit;
+ parents[1] = i_commit;
+ parents[2] = u_commit;
+
+ if (git_commit_tree(&i_tree, i_commit) < 0)
+ return -1;
+
+ if (git_index_read_tree(index, i_tree) < 0)
+ goto cleanup;
+
+ if (build_workdir_tree(&w_tree, index, b_commit) < 0)
+ goto cleanup;
+
+ if (git_commit_create(
+ w_commit_oid,
+ git_index_owner(index),
+ NULL,
+ stasher,
+ stasher,
+ NULL,
+ message,
+ w_tree,
+ u_commit ? 3 : 2, parents) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_tree_free(i_tree);
+ git_tree_free(w_tree);
+ return error;
+}
+
+static int prepare_worktree_commit_message(
+ git_buf* msg,
+ const char *user_message)
+{
+ git_buf buf = GIT_BUF_INIT;
+ int error = -1;
+
+ git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg));
+ git_buf_clear(msg);
+
+ if (!user_message)
+ git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf));
+ else {
+ const char *colon;
+
+ if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL)
+ goto cleanup;
+
+ git_buf_puts(msg, "On ");
+ git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr);
+ git_buf_printf(msg, ": %s\n", user_message);
+ }
+
+ error = git_buf_oom(msg) || git_buf_oom(&buf) ? -1 : 0;
+
+cleanup:
+ git_buf_free(&buf);
+ return error;
+}
+
+static int update_reflog(
+ git_oid *w_commit_oid,
+ git_repository *repo,
+ git_signature *stasher,
+ const char *message)
+{
+ git_reference *stash = NULL;
+ git_reflog *reflog = NULL;
+ int error;
+
+ if ((error = git_reference_create_oid(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, stash)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_append(reflog, w_commit_oid, stasher, message)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_write(reflog)) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int is_dirty_cb(const char *path, unsigned int status, void *payload)
+{
+ GIT_UNUSED(path);
+ GIT_UNUSED(status);
+ GIT_UNUSED(payload);
+
+ return 1;
+}
+
+static int ensure_there_are_changes_to_stash(
+ git_repository *repo,
+ bool include_untracked_files,
+ bool include_ignored_files)
+{
+ int error;
+ git_status_options opts;
+
+ memset(&opts, 0, sizeof(opts));
+ opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
+ if (include_untracked_files)
+ opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+ GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
+
+ if (include_ignored_files)
+ opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED;
+
+ error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL);
+
+ if (error == GIT_EUSER)
+ return 0;
+
+ if (!error)
+ return create_error(GIT_ENOTFOUND, "There is nothing to stash.");
+
+ return error;
+}
+
+static int reset_index_and_workdir(
+ git_repository *repo,
+ git_commit *commit,
+ bool remove_untracked)
+{
+ git_checkout_opts opts;
+
+ memset(&opts, 0, sizeof(git_checkout_opts));
+
+ opts.checkout_strategy =
+ GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED;
+
+ if (remove_untracked)
+ opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
+
+ return git_checkout_tree(repo, (git_object *)commit, &opts);
+}
+
+int git_stash_save(
+ git_oid *out,
+ git_repository *repo,
+ git_signature *stasher,
+ const char *message,
+ uint32_t flags)
+{
+ git_index *index = NULL;
+ git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL;
+ git_buf msg = GIT_BUF_INIT;
+ int error;
+
+ assert(out && repo && stasher);
+
+ if ((error = ensure_non_bare_repository(repo)) < 0)
+ return error;
+
+ if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0)
+ goto cleanup;
+
+ if ((error = ensure_there_are_changes_to_stash(
+ repo,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) == GIT_STASH_INCLUDE_UNTRACKED,
+ (flags & GIT_STASH_INCLUDE_IGNORED) == GIT_STASH_INCLUDE_IGNORED)) < 0)
+ goto cleanup;
+
+ error = -1;
+
+ if (git_repository_index(&index, repo) < 0)
+ goto cleanup;
+
+ if (commit_index(&i_commit, index, stasher, git_buf_cstr(&msg), b_commit) < 0)
+ goto cleanup;
+
+ if ((flags & GIT_STASH_INCLUDE_UNTRACKED || flags & GIT_STASH_INCLUDE_IGNORED)
+ && commit_untracked(&u_commit, index, stasher, git_buf_cstr(&msg), i_commit, flags) < 0)
+ goto cleanup;
+
+ if (prepare_worktree_commit_message(&msg, message) < 0)
+ goto cleanup;
+
+ if (commit_worktree(out, index, stasher, git_buf_cstr(&msg), i_commit, b_commit, u_commit) < 0)
+ goto cleanup;
+
+ git_buf_rtrim(&msg);
+ if (update_reflog(out, repo, stasher, git_buf_cstr(&msg)) < 0)
+ goto cleanup;
+
+ if (reset_index_and_workdir(
+ repo,
+ ((flags & GIT_STASH_KEEP_INDEX) == GIT_STASH_KEEP_INDEX) ?
+ i_commit : b_commit,
+ (flags & GIT_STASH_INCLUDE_UNTRACKED) == GIT_STASH_INCLUDE_UNTRACKED) < 0)
+ goto cleanup;
+
+ error = 0;
+
+cleanup:
+ git_buf_free(&msg);
+ git_commit_free(i_commit);
+ git_commit_free(b_commit);
+ git_commit_free(u_commit);
+ git_index_free(index);
+ return error;
+}
+
+int git_stash_foreach(
+ git_repository *repo,
+ stash_cb callback,
+ void *payload)
+{
+ git_reference *stash;
+ git_reflog *reflog = NULL;
+ int error;
+ size_t i, max;
+ const git_reflog_entry *entry;
+
+ error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE);
+ if (error == GIT_ENOTFOUND)
+ return 0;
+
+ if (error < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, stash)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+ for (i = 0; i < max; i++) {
+ entry = git_reflog_entry_byindex(reflog, i);
+
+ if (callback(i,
+ git_reflog_entry_msg(entry),
+ git_reflog_entry_oidnew(entry),
+ payload)) {
+ error = GIT_EUSER;
+ goto cleanup;
+ }
+ }
+
+ error = 0;
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
+
+int git_stash_drop(
+ git_repository *repo,
+ size_t index)
+{
+ git_reference *stash;
+ git_reflog *reflog = NULL;
+ size_t max;
+ int error;
+
+ if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
+ return error;
+
+ if ((error = git_reflog_read(&reflog, stash)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+
+ if (index > max - 1) {
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
+ goto cleanup;
+ }
+
+ if ((error = git_reflog_drop(reflog, index, true)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_write(reflog)) < 0)
+ goto cleanup;
+
+ if (max == 1) {
+ error = git_reference_delete(stash);
+ stash = NULL;
+ }
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
diff --git a/src/status.c b/src/status.c
index f57100d11..b8c15ef92 100644
--- a/src/status.c
+++ b/src/status.c
@@ -17,6 +17,7 @@
#include "git2/diff.h"
#include "diff.h"
+#include "diff_output.h"
static unsigned int index_delta2status(git_delta_t index_status)
{
@@ -76,21 +77,43 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status)
return st;
}
+typedef struct {
+ int (*cb)(const char *, unsigned int, void *);
+ void *cbdata;
+} status_user_callback;
+
+static int status_invoke_cb(
+ void *cbref, git_diff_delta *i2h, git_diff_delta *w2i)
+{
+ status_user_callback *usercb = cbref;
+ const char *path = NULL;
+ unsigned int status = 0;
+
+ if (w2i) {
+ path = w2i->old_file.path;
+ status |= workdir_delta2status(w2i->status);
+ }
+ if (i2h) {
+ path = i2h->old_file.path;
+ status |= index_delta2status(i2h->status);
+ }
+
+ return usercb->cb(path, status, usercb->cbdata);
+}
+
int git_status_foreach_ext(
git_repository *repo,
const git_status_options *opts,
int (*cb)(const char *, unsigned int, void *),
void *cbdata)
{
- int err = 0, cmp;
+ int err = 0;
git_diff_options diffopt;
git_diff_list *idx2head = NULL, *wd2idx = NULL;
git_tree *head = NULL;
git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
- git_diff_delta *i2h, *w2i;
- size_t i, j, i_max, j_max;
- bool ignore_case = false;
+ status_user_callback usercb;
assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR);
@@ -119,62 +142,26 @@ int git_status_foreach_ext(
/* TODO: support EXCLUDE_SUBMODULES flag */
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
- (err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0)
+ (err = git_diff_index_to_tree(&idx2head, repo, head, NULL, &diffopt)) < 0)
goto cleanup;
if (show != GIT_STATUS_SHOW_INDEX_ONLY &&
- (err = git_diff_workdir_to_index(repo, &diffopt, &wd2idx)) < 0)
+ (err = git_diff_workdir_to_index(&wd2idx, repo, NULL, &diffopt)) < 0)
goto cleanup;
+ usercb.cb = cb;
+ usercb.cbdata = cbdata;
+
if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) {
- for (i = 0; !err && i < idx2head->deltas.length; i++) {
- i2h = GIT_VECTOR_GET(&idx2head->deltas, i);
- if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata))
- err = GIT_EUSER;
- }
+ if ((err = git_diff__paired_foreach(
+ idx2head, NULL, status_invoke_cb, &usercb)) < 0)
+ goto cleanup;
+
git_diff_list_free(idx2head);
idx2head = NULL;
}
- i_max = idx2head ? idx2head->deltas.length : 0;
- j_max = wd2idx ? wd2idx->deltas.length : 0;
-
- if (idx2head && wd2idx &&
- (0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) ||
- 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)))
- {
- /* Then use the ignore-case sorter... */
- ignore_case = true;
-
- /* and assert that both are ignore-case sorted. If this function
- * ever needs to support merge joining result sets that are not sorted
- * by the same function, then it will need to be extended to do a spool
- * and sort on one of the results before merge joining */
- assert(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) &&
- 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE));
- }
-
- for (i = 0, j = 0; !err && (i < i_max || j < j_max); ) {
- i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL;
- w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL;
-
- cmp = !w2i ? -1 : !i2h ? 1 : STRCMP_CASESELECT(ignore_case, i2h->old_file.path, w2i->old_file.path);
-
- if (cmp < 0) {
- if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata))
- err = GIT_EUSER;
- i++;
- } else if (cmp > 0) {
- if (cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata))
- err = GIT_EUSER;
- j++;
- } else {
- if (cb(i2h->old_file.path, index_delta2status(i2h->status) |
- workdir_delta2status(w2i->status), cbdata))
- err = GIT_EUSER;
- i++; j++;
- }
- }
+ err = git_diff__paired_foreach(idx2head, wd2idx, status_invoke_cb, &usercb);
cleanup:
git_tree_free(head);
@@ -217,7 +204,7 @@ static int get_one_status(const char *path, unsigned int status, void *data)
sfi->count++;
sfi->status = status;
- if (sfi->count > 1 ||
+ if (sfi->count > 1 ||
(strcmp(sfi->expected, path) != 0 &&
p_fnmatch(sfi->expected, path, 0) != 0)) {
giterr_set(GITERR_INVALID,
diff --git a/src/submodule.c b/src/submodule.c
index e3657f9ad..6eb1c52f7 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -332,7 +332,7 @@ int git_submodule_add_finalize(git_submodule *sm)
assert(sm);
if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 ||
- (error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0)
+ (error = git_index_add_from_workdir(index, GIT_MODULES_FILE)) < 0)
return error;
return git_submodule_add_to_index(sm, true);
@@ -371,7 +371,7 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index)
memset(&entry, 0, sizeof(entry));
entry.path = sm->path;
- git_index__init_entry_from_stat(&st, &entry);
+ git_index_entry__init_from_stat(&entry, &st);
/* calling git_submodule_open will have set sm->wd_oid if possible */
if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) {
@@ -393,7 +393,7 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index)
git_commit_free(head);
/* add it */
- error = git_index_add2(index, &entry);
+ error = git_index_add(index, &entry);
/* write it, if requested */
if (!error && write_index) {
@@ -733,7 +733,7 @@ int git_submodule_reload(git_submodule *submodule)
pos = git_index_find(index, submodule->path);
if (pos >= 0) {
- git_index_entry *entry = git_index_get(index, pos);
+ git_index_entry *entry = git_index_get_byindex(index, pos);
if (S_ISGITLINK(entry->mode)) {
if ((error = submodule_load_from_index(repo, entry)) < 0)
@@ -1118,7 +1118,7 @@ static int load_submodule_config_from_index(
git_iterator *i;
const git_index_entry *entry;
- if ((error = git_iterator_for_index(&i, repo)) < 0)
+ if ((error = git_iterator_for_repo_index(&i, repo)) < 0)
return error;
error = git_iterator_current(i, &entry);
@@ -1455,7 +1455,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE)
opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
- error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff);
+ error = git_diff_index_to_tree(&diff, sm_repo, sm_head, NULL, &opt);
if (!error) {
if (git_diff_num_deltas(diff) > 0)
@@ -1472,7 +1472,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
/* perform index-to-workdir diff on submodule */
- error = git_diff_workdir_to_index(sm_repo, &opt, &diff);
+ error = git_diff_workdir_to_index(&diff, sm_repo, NULL, &opt);
if (!error) {
size_t untracked =
diff --git a/src/tag.c b/src/tag.c
index 56f84a85f..13369d9fb 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -39,7 +39,7 @@ const git_oid *git_tag_target_oid(git_tag *t)
return &t->target;
}
-git_otype git_tag_type(git_tag *t)
+git_otype git_tag_target_type(git_tag *t)
{
assert(t);
return t->type;
@@ -139,16 +139,19 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length)
return -1;
}
- if( *buffer != '\n' )
- return tag_error("No new line before message");
+ tag->message = NULL;
+ if (buffer < buffer_end) {
+ if( *buffer != '\n' )
+ return tag_error("No new line before message");
- text_len = buffer_end - ++buffer;
+ text_len = buffer_end - ++buffer;
- tag->message = git__malloc(text_len + 1);
- GITERR_CHECK_ALLOC(tag->message);
+ tag->message = git__malloc(text_len + 1);
+ GITERR_CHECK_ALLOC(tag->message);
- memcpy(tag->message, buffer, text_len);
- tag->message[text_len] = '\0';
+ memcpy(tag->message, buffer, text_len);
+ tag->message[text_len] = '\0';
+ }
return 0;
}
diff --git a/src/transport.c b/src/transport.c
index fb2b94946..8c242af6d 100644
--- a/src/transport.c
+++ b/src/transport.c
@@ -8,52 +8,78 @@
#include "git2/types.h"
#include "git2/remote.h"
#include "git2/net.h"
-#include "transport.h"
+#include "git2/transport.h"
#include "path.h"
-static struct {
+typedef struct transport_definition {
char *prefix;
+ unsigned priority;
git_transport_cb fn;
-} transports[] = {
- {"git://", git_transport_git},
- {"http://", git_transport_http},
- {"https://", git_transport_https},
- {"file://", git_transport_local},
- {"git+ssh://", git_transport_dummy},
- {"ssh+git://", git_transport_dummy},
- {NULL, 0}
+ void *param;
+} transport_definition;
+
+static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL };
+static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL };
+
+static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 };
+static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 };
+
+static transport_definition transports[] = {
+ {"git://", 1, git_transport_smart, &git_subtransport_definition},
+ {"http://", 1, git_transport_smart, &http_subtransport_definition},
+ {"https://", 1, git_transport_smart, &http_subtransport_definition},
+ {"file://", 1, git_transport_local, NULL},
+ {"git+ssh://", 1, git_transport_dummy, NULL},
+ {"ssh+git://", 1, git_transport_dummy, NULL},
+ {NULL, 0, 0}
};
#define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1
-static git_transport_cb transport_find_fn(const char *url)
+static int transport_find_fn(const char *url, git_transport_cb *callback, void **param)
{
size_t i = 0;
+ unsigned priority = 0;
+ transport_definition *definition = NULL, *definition_iter;
// First, check to see if it's an obvious URL, which a URL scheme
for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) {
- if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix)))
- return transports[i].fn;
+ definition_iter = &transports[i];
+
+ if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix)))
+ continue;
+
+ if (definition_iter->priority > priority)
+ definition = definition_iter;
}
- /* still here? Check to see if the path points to a file on the local file system */
- if ((git_path_exists(url) == 0) && git_path_isdir(url))
- return &git_transport_local;
+ if (!definition) {
+ /* still here? Check to see if the path points to a file on the local file system */
+ if ((git_path_exists(url) == 0) && git_path_isdir(url))
+ definition = &local_transport_definition;
+
+ /* It could be a SSH remote path. Check to see if there's a : */
+ if (strrchr(url, ':'))
+ definition = &dummy_transport_definition; /* SSH is an unsupported transport mechanism in this version of libgit2 */
+ }
- /* It could be a SSH remote path. Check to see if there's a : */
- if (strrchr(url, ':'))
- return &git_transport_dummy; /* SSH is an unsupported transport mechanism in this version of libgit2 */
+ if (!definition)
+ return -1;
- return NULL;
+ *callback = definition->fn;
+ *param = definition->param;
+
+ return 0;
}
/**************
* Public API *
**************/
-int git_transport_dummy(git_transport **transport)
+int git_transport_dummy(git_transport **transport, void *param)
{
GIT_UNUSED(transport);
+ GIT_UNUSED(param);
giterr_set(GITERR_NET, "This transport isn't implemented. Sorry");
return -1;
}
@@ -62,22 +88,18 @@ int git_transport_new(git_transport **out, const char *url)
{
git_transport_cb fn;
git_transport *transport;
+ void *param;
int error;
- fn = transport_find_fn(url);
-
- if (fn == NULL) {
+ if (transport_find_fn(url, &fn, &param) < 0) {
giterr_set(GITERR_NET, "Unsupported URL protocol");
return -1;
}
- error = fn(&transport);
+ error = fn(&transport, param);
if (error < 0)
return error;
- transport->url = git__strdup(url);
- GITERR_CHECK_ALLOC(transport->url);
-
*out = transport;
return 0;
@@ -86,12 +108,19 @@ int git_transport_new(git_transport **out, const char *url)
/* from remote.h */
int git_remote_valid_url(const char *url)
{
- return transport_find_fn(url) != NULL;
+ git_transport_cb fn;
+ void *param;
+
+ return !transport_find_fn(url, &fn, &param);
}
int git_remote_supported_url(const char* url)
{
- git_transport_cb transport_fn = transport_find_fn(url);
+ git_transport_cb fn;
+ void *param;
+
+ if (transport_find_fn(url, &fn, &param) < 0)
+ return 0;
- return ((transport_fn != NULL) && (transport_fn != &git_transport_dummy));
+ return fn != &git_transport_dummy;
}
diff --git a/src/transport.h b/src/transport.h
deleted file mode 100644
index 4c944b9e7..000000000
--- a/src/transport.h
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2009-2012 the libgit2 contributors
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-#ifndef INCLUDE_transport_h__
-#define INCLUDE_transport_h__
-
-#include "git2/net.h"
-#include "git2/indexer.h"
-#include "vector.h"
-#include "posix.h"
-#include "common.h"
-#include "netops.h"
-#ifdef GIT_SSL
-# include <openssl/ssl.h>
-# include <openssl/err.h>
-#endif
-
-
-#define GIT_CAP_OFS_DELTA "ofs-delta"
-#define GIT_CAP_MULTI_ACK "multi_ack"
-#define GIT_CAP_SIDE_BAND "side-band"
-#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
-#define GIT_CAP_INCLUDE_TAG "include-tag"
-
-typedef struct git_transport_caps {
- int common:1,
- ofs_delta:1,
- multi_ack: 1,
- side_band:1,
- side_band_64k:1,
- include_tag:1;
-} git_transport_caps;
-
-#ifdef GIT_SSL
-typedef struct gitno_ssl {
- SSL_CTX *ctx;
- SSL *ssl;
-} gitno_ssl;
-#endif
-
-
-/*
- * A day in the life of a network operation
- * ========================================
- *
- * The library gets told to ls-remote/push/fetch on/to/from some
- * remote. We look at the URL of the remote and fill the function
- * table with whatever is appropriate (the remote may be git over git,
- * ssh or http(s). It may even be an hg or svn repository, the library
- * at this level doesn't care, it just calls the helpers.
- *
- * The first call is to ->connect() which connects to the remote,
- * making use of the direction if necessary. This function must also
- * store the remote heads and any other information it needs.
- *
- * The next useful step is to call ->ls() to get the list of
- * references available to the remote. These references may have been
- * collected on connect, or we may build them now. For ls-remote,
- * nothing else is needed other than closing the connection.
- * Otherwise, the higher leves decide which objects we want to
- * have. ->send_have() is used to tell the other end what we have. If
- * we do need to download a pack, ->download_pack() is called.
- *
- * When we're done, we call ->close() to close the
- * connection. ->free() takes care of freeing all the resources.
- */
-
-struct git_transport {
- /**
- * Where the repo lives
- */
- char *url;
- /**
- * Whether we want to push or fetch
- */
- int direction : 1, /* 0 fetch, 1 push */
- connected : 1,
- check_cert: 1,
- use_ssl : 1,
- own_logic: 1, /* transitional */
- rpc: 1; /* git-speak for the HTTP transport */
-#ifdef GIT_SSL
- struct gitno_ssl ssl;
-#endif
- git_vector refs;
- git_vector common;
- gitno_buffer buffer;
- GIT_SOCKET socket;
- git_transport_caps caps;
- void *cb_data;
- git_atomic cancel;
-
- /**
- * Connect and store the remote heads
- */
- int (*connect)(struct git_transport *transport, int dir);
- /**
- * Send our side of a negotiation
- */
- int (*negotiation_step)(struct git_transport *transport, void *data, size_t len);
- /**
- * Push the changes over
- */
- int (*push)(struct git_transport *transport);
- /**
- * Negotiate the minimal amount of objects that need to be
- * retrieved
- */
- int (*negotiate_fetch)(struct git_transport *transport, git_repository *repo, const git_vector *wants);
- /**
- * Download the packfile
- */
- int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats);
- /**
- * Close the connection
- */
- int (*close)(struct git_transport *transport);
- /**
- * Free the associated resources
- */
- void (*free)(struct git_transport *transport);
- /**
- * Callbacks for the progress and error output
- */
- void (*progress_cb)(const char *str, int len, void *data);
- void (*error_cb)(const char *str, int len, void *data);
-};
-
-
-int git_transport_new(struct git_transport **transport, const char *url);
-int git_transport_local(struct git_transport **transport);
-int git_transport_git(struct git_transport **transport);
-int git_transport_http(struct git_transport **transport);
-int git_transport_https(struct git_transport **transport);
-int git_transport_dummy(struct git_transport **transport);
-
-/**
- Returns true if the passed URL is valid (a URL with a Git supported scheme,
- or pointing to an existing path)
-*/
-int git_transport_valid_url(const char *url);
-
-typedef int (*git_transport_cb)(git_transport **transport);
-
-#endif
diff --git a/src/transports/cred.c b/src/transports/cred.c
new file mode 100644
index 000000000..55295372f
--- /dev/null
+++ b/src/transports/cred.c
@@ -0,0 +1,57 @@
+/*
+ * 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 "smart.h"
+
+static void plaintext_free(struct git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ int pass_len = strlen(c->password);
+
+ git__free(c->username);
+
+ /* Zero the memory which previously held the password */
+ memset(c->password, 0x0, pass_len);
+ git__free(c->password);
+
+ git__free(c);
+}
+
+int git_cred_userpass_plaintext_new(
+ git_cred **cred,
+ const char *username,
+ const char *password)
+{
+ git_cred_userpass_plaintext *c;
+
+ if (!cred)
+ return -1;
+
+ c = (git_cred_userpass_plaintext *)git__malloc(sizeof(git_cred_userpass_plaintext));
+ GITERR_CHECK_ALLOC(c);
+
+ c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ c->parent.free = plaintext_free;
+ c->username = git__strdup(username);
+
+ if (!c->username) {
+ git__free(c);
+ return -1;
+ }
+
+ c->password = git__strdup(password);
+
+ if (!c->password) {
+ git__free(c->username);
+ git__free(c);
+ return -1;
+ }
+
+ *cred = &c->parent;
+ return 0;
+} \ No newline at end of file
diff --git a/src/transports/git.c b/src/transports/git.c
index b757495c5..a895c1389 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -5,40 +5,37 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include "git2/net.h"
-#include "git2/common.h"
-#include "git2/types.h"
-#include "git2/errors.h"
-#include "git2/net.h"
-#include "git2/revwalk.h"
-
-#include "vector.h"
-#include "transport.h"
-#include "pkt.h"
-#include "common.h"
+#include "git2.h"
+#include "buffer.h"
#include "netops.h"
-#include "filebuf.h"
-#include "repository.h"
-#include "fetch.h"
-#include "protocol.h"
+
+#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)
+
+static const char prefix_git[] = "git://";
+static const char cmd_uploadpack[] = "git-upload-pack";
typedef struct {
- git_transport parent;
- char buff[65536];
-#ifdef GIT_WIN32
- WSADATA wsd;
-#endif
-} transport_git;
+ git_smart_subtransport_stream parent;
+ gitno_socket socket;
+ const char *cmd;
+ char *url;
+ unsigned sent_command : 1;
+} git_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ git_transport *owner;
+ git_stream *current_stream;
+} git_subtransport;
/*
- * Create a git procol request.
+ * Create a git protocol request.
*
* For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
*/
static int gen_proto(git_buf *request, const char *cmd, const char *url)
{
char *delim, *repo;
- char default_command[] = "git-upload-pack";
char host[] = "host=";
size_t len;
@@ -54,9 +51,6 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
if (delim == NULL)
delim = strchr(url, '/');
- if (cmd == NULL)
- cmd = default_command;
-
len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
git_buf_grow(request, len);
@@ -71,175 +65,211 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
return 0;
}
-static int send_request(git_transport *t, const char *cmd, const char *url)
+static int send_command(git_stream *s)
{
int error;
git_buf request = GIT_BUF_INIT;
- error = gen_proto(&request, cmd, url);
+ error = gen_proto(&request, s->cmd, s->url);
if (error < 0)
goto cleanup;
- error = gitno_send(t, request.ptr, request.size, 0);
+ /* It looks like negative values are errors here, and positive values
+ * are the number of bytes sent. */
+ error = gitno_send(&s->socket, request.ptr, request.size, 0);
+
+ if (error >= 0)
+ s->sent_command = 1;
cleanup:
git_buf_free(&request);
return error;
}
-/*
- * Parse the URL and connect to a server, storing the socket in
- * out. For convenience this also takes care of asking for the remote
- * refs
- */
-static int do_connect(transport_git *t, const char *url)
+static int git_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
{
- char *host, *port;
- const char prefix[] = "git://";
+ git_stream *s = (git_stream *)stream;
+ gitno_buffer buf;
- if (!git__prefixcmp(url, prefix))
- url += strlen(prefix);
+ *bytes_read = 0;
- if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
+ if (!s->sent_command && send_command(s) < 0)
return -1;
- if (gitno_connect((git_transport *)t, host, port) < 0)
- goto on_error;
+ gitno_buffer_setup(&s->socket, &buf, buffer, buf_size);
- if (send_request((git_transport *)t, NULL, url) < 0)
- goto on_error;
+ if (gitno_recv(&buf) < 0)
+ return -1;
- git__free(host);
- git__free(port);
+ *bytes_read = buf.offset;
return 0;
-
-on_error:
- git__free(host);
- git__free(port);
- gitno_close(t->parent.socket);
- return -1;
}
-/*
- * Since this is a network connection, we need to parse and store the
- * pkt-lines at this stage and keep them there.
- */
-static int git_connect(git_transport *transport, int direction)
+static int git_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
{
- transport_git *t = (transport_git *) transport;
+ git_stream *s = (git_stream *)stream;
- if (direction == GIT_DIR_PUSH) {
- giterr_set(GITERR_NET, "Pushing over git:// is not supported");
+ if (!s->sent_command && send_command(s) < 0)
return -1;
+
+ return gitno_send(&s->socket, buffer, len, 0);
+}
+
+static void git_stream_free(git_smart_subtransport_stream *stream)
+{
+ git_stream *s = (git_stream *)stream;
+ git_subtransport *t = OWNING_SUBTRANSPORT(s);
+ int ret;
+
+ GIT_UNUSED(ret);
+
+ t->current_stream = NULL;
+
+ if (s->socket.socket) {
+ ret = gitno_close(&s->socket);
+ assert(!ret);
}
- t->parent.direction = direction;
+ git__free(s->url);
+ git__free(s);
+}
- /* Connect and ask for the refs */
- if (do_connect(t, transport->url) < 0)
+static int git_stream_alloc(
+ git_subtransport *t,
+ const char *url,
+ const char *cmd,
+ git_smart_subtransport_stream **stream)
+{
+ git_stream *s;
+
+ if (!stream)
return -1;
- gitno_buffer_setup(transport, &transport->buffer, t->buff, sizeof(t->buff));
+ s = (git_stream *)git__calloc(sizeof(git_stream), 1);
+ GITERR_CHECK_ALLOC(s);
- t->parent.connected = 1;
- if (git_protocol_store_refs(transport, 1) < 0)
- return -1;
+ s->parent.subtransport = &t->parent;
+ s->parent.read = git_stream_read;
+ s->parent.write = git_stream_write;
+ s->parent.free = git_stream_free;
- if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
+ s->cmd = cmd;
+ s->url = git__strdup(url);
+
+ if (!s->url) {
+ git__free(s);
return -1;
+ }
+ *stream = &s->parent;
return 0;
}
-static int git_negotiation_step(struct git_transport *transport, void *data, size_t len)
+static int git_git_uploadpack_ls(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- return gitno_send(transport, data, len, 0);
-}
+ char *host, *port;
+ git_stream *s;
-static int git_close(git_transport *t)
-{
- git_buf buf = GIT_BUF_INIT;
+ *stream = NULL;
- if (git_pkt_buffer_flush(&buf) < 0)
- return -1;
- /* Can't do anything if there's an error, so don't bother checking */
- gitno_send(t, buf.ptr, buf.size, 0);
- git_buf_free(&buf);
+ if (!git__prefixcmp(url, prefix_git))
+ url += strlen(prefix_git);
- if (gitno_close(t->socket) < 0) {
- giterr_set(GITERR_NET, "Failed to close socket");
+ if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0)
return -1;
- }
- t->connected = 0;
+ s = (git_stream *)*stream;
+
+ if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
+ goto on_error;
-#ifdef GIT_WIN32
- WSACleanup();
-#endif
+ 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 void git_free(git_transport *transport)
+static int git_git_uploadpack(
+ git_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
{
- transport_git *t = (transport_git *) transport;
- git_vector *refs = &transport->refs;
- unsigned int i;
+ GIT_UNUSED(url);
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- git_pkt_free(p);
+ if (t->current_stream) {
+ *stream = &t->current_stream->parent;
+ return 0;
}
- git_vector_free(refs);
- refs = &transport->common;
- for (i = 0; i < refs->length; ++i) {
- git_pkt *p = git_vector_get(refs, i);
- git_pkt_free(p);
+ giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
+ return -1;
+}
+
+static int _git_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *smart_transport,
+ const char *url,
+ git_smart_service_t action)
+{
+ git_subtransport *t = (git_subtransport *) smart_transport;
+
+ switch (action) {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return git_git_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return git_git_uploadpack(t, url, stream);
}
- git_vector_free(refs);
- git__free(t->parent.url);
- git__free(t);
+ *stream = NULL;
+ return -1;
}
-int git_transport_git(git_transport **out)
+static void _git_free(git_smart_subtransport *smart_transport)
{
- transport_git *t;
-#ifdef GIT_WIN32
- int ret;
-#endif
+ git_subtransport *t = (git_subtransport *) smart_transport;
- t = git__malloc(sizeof(transport_git));
- GITERR_CHECK_ALLOC(t);
+ assert(!t->current_stream);
- memset(t, 0x0, sizeof(transport_git));
- if (git_vector_init(&t->parent.common, 8, NULL))
- goto on_error;
+ git__free(t);
+}
- if (git_vector_init(&t->parent.refs, 16, NULL) < 0)
- goto on_error;
+int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner)
+{
+ git_subtransport *t;
- t->parent.connect = git_connect;
- t->parent.negotiation_step = git_negotiation_step;
- t->parent.close = git_close;
- t->parent.free = git_free;
+ if (!out)
+ return -1;
- *out = (git_transport *) t;
+ t = (git_subtransport *)git__calloc(sizeof(git_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
-#ifdef GIT_WIN32
- ret = WSAStartup(MAKEWORD(2,2), &t->wsd);
- if (ret != 0) {
- git_free(*out);
- giterr_set(GITERR_NET, "Winsock init failed");
- return -1;
- }
-#endif
+ t->owner = owner;
+ t->parent.action = _git_action;
+ t->parent.free = _git_free;
+ *out = (git_smart_subtransport *) t;
return 0;
-
-on_error:
- git__free(t);
- return -1;
}
diff --git a/src/transports/http.c b/src/transports/http.c
index 93dd0c326..ba4d8746f 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -4,28 +4,27 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
-#include <stdlib.h>
+#ifndef GIT_WINHTTP
+
#include "git2.h"
#include "http_parser.h"
-
-#include "transport.h"
-#include "common.h"
-#include "netops.h"
#include "buffer.h"
-#include "pkt.h"
-#include "refs.h"
-#include "pack.h"
-#include "fetch.h"
-#include "filebuf.h"
-#include "repository.h"
-#include "protocol.h"
-#if GIT_WINHTTP
-# include <winhttp.h>
-# pragma comment(lib, "winhttp.lib")
-#endif
-
-#define WIDEN2(s) L ## s
-#define WIDEN(s) WIDEN2(s)
+#include "netops.h"
+#include "smart.h"
+
+static const char *prefix_http = "http://";
+static const char *prefix_https = "https://";
+static const char *upload_pack_service = "upload-pack";
+static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
+static const char *upload_pack_service_url = "/git-upload-pack";
+static const char *get_verb = "GET";
+static const char *post_verb = "POST";
+static const char *basic_authtype = "Basic";
+
+#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
+
+#define PARSE_ERROR_GENERIC -1
+#define PARSE_ERROR_REPLAY -2
enum last_cb {
NONE,
@@ -33,44 +32,93 @@ enum last_cb {
VALUE
};
+typedef enum {
+ GIT_HTTP_AUTH_BASIC = 1,
+} http_authmechanism_t;
+
typedef struct {
- git_transport parent;
- http_parser_settings settings;
- git_buf buf;
- int error;
- int transfer_finished :1,
- ct_found :1,
- ct_finished :1,
- pack_ready :1;
- enum last_cb last_cb;
- http_parser parser;
- char *content_type;
- char *path;
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ const char *verb;
+ unsigned sent_request : 1;
+} http_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ gitno_socket socket;
+ const char *path;
char *host;
char *port;
- char *service;
- char buffer[65536];
-#ifdef GIT_WIN32
- WSADATA wsd;
-#endif
-#ifdef GIT_WINHTTP
- HINTERNET session;
- HINTERNET connection;
- HINTERNET request;
-#endif
-} transport_http;
-
-static int gen_request(git_buf *buf, const char *path, const char *host, const char *op,
- const char *service, ssize_t content_length, int ls)
+ git_cred *cred;
+ http_authmechanism_t auth_mechanism;
+ unsigned connected : 1,
+ use_ssl : 1;
+
+ /* Parser structures */
+ http_parser parser;
+ http_parser_settings settings;
+ gitno_buffer parse_buffer;
+ git_buf parse_header_name;
+ git_buf parse_header_value;
+ char parse_buffer_data[2048];
+ char *content_type;
+ git_vector www_authenticate;
+ enum last_cb last_cb;
+ int parse_error;
+ unsigned parse_finished : 1;
+} http_subtransport;
+
+typedef struct {
+ http_stream *s;
+ http_subtransport *t;
+
+ /* Target buffer details from read() */
+ char *buffer;
+ size_t buf_size;
+ size_t *bytes_read;
+} parser_context;
+
+static int apply_basic_credential(git_buf *buf, git_cred *cred)
{
- if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ git_buf raw = GIT_BUF_INIT;
+ int error = -1;
+
+ git_buf_printf(&raw, "%s:%s", c->username, c->password);
+
+ if (git_buf_oom(&raw) ||
+ git_buf_puts(buf, "Authorization: Basic ") < 0 ||
+ git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 ||
+ git_buf_puts(buf, "\r\n") < 0)
+ goto on_error;
+
+ error = 0;
+
+on_error:
+ if (raw.size)
+ memset(raw.ptr, 0x0, raw.size);
+
+ git_buf_free(&raw);
+ return error;
+}
+
+static int gen_request(
+ git_buf *buf,
+ 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)
+{
+ if (!path)
path = "/";
- if (ls) {
- git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service);
- } else {
- git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service);
- }
+ git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, 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) {
@@ -80,6 +128,13 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c
} 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)
+ return -1;
+
git_buf_puts(buf, "\r\n");
if (git_buf_oom(buf))
@@ -88,538 +143,508 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c
return 0;
}
-static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls)
+static int parse_unauthorized_response(
+ git_vector *www_authenticate,
+ int *allowed_types,
+ http_authmechanism_t *auth_mechanism)
{
-#ifndef GIT_WINHTTP
- git_buf request = GIT_BUF_INIT;
- const char *verb;
- int error = -1;
-
- verb = ls ? "GET" : "POST";
- /* Generate and send the HTTP request */
- if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) {
- giterr_set(GITERR_NET, "Failed to generate request");
- return -1;
+ unsigned i;
+ char *entry;
+
+ git_vector_foreach(www_authenticate, i, entry) {
+ if (!strncmp(entry, basic_authtype, 5) &&
+ (entry[5] == '\0' || entry[5] == ' ')) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_HTTP_AUTH_BASIC;
+ }
}
+ return 0;
+}
- if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0)
- goto cleanup;
+static int on_header_ready(http_subtransport *t)
+{
+ git_buf *name = &t->parse_header_name;
+ git_buf *value = &t->parse_header_value;
+ char *dup;
- if (content_length) {
- if (gitno_send((git_transport *) t, data, content_length, 0) < 0)
- goto cleanup;
+ if (!t->content_type && !strcmp("Content-Type", git_buf_cstr(name))) {
+ t->content_type = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(t->content_type);
+ }
+ else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) {
+ dup = git__strdup(git_buf_cstr(value));
+ GITERR_CHECK_ALLOC(dup);
+ git_vector_insert(&t->www_authenticate, dup);
}
- error = 0;
+ return 0;
+}
-cleanup:
- git_buf_free(&request);
- return error;
+static int on_header_field(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
-#else
- wchar_t *verb;
- wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH];
+ if (NONE == t->last_cb || VALUE == t->last_cb)
+ git_buf_clear(&t->parse_header_name);
+
+ if (git_buf_put(&t->parse_header_name, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ t->last_cb = FIELD;
+ return 0;
+}
+
+static int on_header_value(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+
+ assert(NONE != t->last_cb);
+
+ if (FIELD == t->last_cb)
+ git_buf_clear(&t->parse_header_value);
+
+ if (git_buf_put(&t->parse_header_value, str, len) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ t->last_cb = VALUE;
+ return 0;
+}
+
+static int on_headers_complete(http_parser *parser)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
+ http_stream *s = ctx->s;
git_buf buf = GIT_BUF_INIT;
- BOOL ret;
- DWORD flags;
- void *buffer;
- wchar_t *types[] = {
- L"*/*",
- NULL,
- };
-
- verb = ls ? L"GET" : L"POST";
- buffer = data ? data : WINHTTP_NO_REQUEST_DATA;
- flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0;
-
- if (ls)
- git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service);
- else
- git_buf_printf(&buf, "%s/git-%s", t->path, service);
- if (git_buf_oom(&buf))
- return -1;
+ /* Both parse_header_name and parse_header_value are populated
+ * and ready for consumption. */
+ if (VALUE == t->last_cb)
+ if (on_header_ready(t) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
- git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
+ /* Check for an authentication failure. */
+ if (parser->status_code == 401 &&
+ get_verb == s->verb &&
+ t->owner->cred_acquire_cb) {
+ int allowed_types = 0;
- t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags);
- if (t->request == NULL) {
- git_buf_free(&buf);
- giterr_set(GITERR_OS, "Failed to open request");
- return -1;
- }
+ if (parse_unauthorized_response(&t->www_authenticate,
+ &allowed_types, &t->auth_mechanism) < 0)
+ return t->parse_error = PARSE_ERROR_GENERIC;
- git_buf_clear(&buf);
- if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0)
- goto on_error;
+ if (allowed_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
- git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf));
+ if (t->owner->cred_acquire_cb(&t->cred,
+ t->owner->url,
+ allowed_types) < 0)
+ return PARSE_ERROR_GENERIC;
- if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) {
- giterr_set(GITERR_OS, "Failed to add a header to the request");
- goto on_error;
- }
+ assert(t->cred);
- if (!t->parent.check_cert) {
- int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
- if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) {
- giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
- goto on_error;
+ /* Successfully acquired a credential. */
+ return t->parse_error = PARSE_ERROR_REPLAY;
}
}
- if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
- data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) {
- giterr_set(GITERR_OS, "Failed to send request");
- goto on_error;
+ /* Check for a 200 HTTP status code. */
+ if (parser->status_code != 200) {
+ giterr_set(GITERR_NET,
+ "Unexpected HTTP status code: %d",
+ parser->status_code);
+ return t->parse_error = PARSE_ERROR_GENERIC;
}
- ret = WinHttpReceiveResponse(t->request, NULL);
- if (ret == FALSE) {
- giterr_set(GITERR_OS, "Failed to receive response");
- goto on_error;
+ /* The response must contain a Content-Type header. */
+ if (!t->content_type) {
+ giterr_set(GITERR_NET, "No Content-Type header in response");
+ return t->parse_error = PARSE_ERROR_GENERIC;
}
- return 0;
+ /* The Content-Type header must match our expectation. */
+ if (get_verb == s->verb)
+ git_buf_printf(&buf,
+ "application/x-git-%s-advertisement",
+ ctx->s->service);
+ else
+ git_buf_printf(&buf,
+ "application/x-git-%s-result",
+ ctx->s->service);
+
+ if (git_buf_oom(&buf))
+ return t->parse_error = PARSE_ERROR_GENERIC;
+
+ if (strcmp(t->content_type, git_buf_cstr(&buf))) {
+ git_buf_free(&buf);
+ giterr_set(GITERR_NET,
+ "Invalid Content-Type: %s",
+ t->content_type);
+ return t->parse_error = PARSE_ERROR_GENERIC;
+ }
-on_error:
git_buf_free(&buf);
- if (t->request)
- WinHttpCloseHandle(t->request);
- t->request = NULL;
- return -1;
-#endif
+
+ return 0;
}
-static int do_connect(transport_http *t)
+static int on_message_complete(http_parser *parser)
{
-#ifndef GIT_WINHTTP
- if (t->parent.connected && http_should_keep_alive(&t->parser))
- return 0;
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
- if (gitno_connect((git_transport *) t, t->host, t->port) < 0)
- return -1;
-
- t->parent.connected = 1;
+ t->parse_finished = 1;
return 0;
-#else
- wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
- wchar_t host[GIT_WIN_PATH];
- int32_t port;
+}
- t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
- WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
+static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
+{
+ parser_context *ctx = (parser_context *) parser->data;
+ http_subtransport *t = ctx->t;
- if (t->session == NULL) {
- giterr_set(GITERR_OS, "Failed to init WinHTTP");
- goto on_error;
+ if (ctx->buf_size < len) {
+ giterr_set(GITERR_NET, "Can't fit data in the buffer");
+ return t->parse_error = PARSE_ERROR_GENERIC;
}
- git__utf8_to_16(host, GIT_WIN_PATH, t->host);
-
- if (git__strtol32(&port, t->port, NULL, 10) < 0)
- goto on_error;
-
- t->connection = WinHttpConnect(t->session, host, port, 0);
- if (t->connection == NULL) {
- giterr_set(GITERR_OS, "Failed to connect to host");
- goto on_error;
- }
+ memcpy(ctx->buffer, str, len);
+ *(ctx->bytes_read) += len;
+ ctx->buffer += len;
+ ctx->buf_size -= len;
- t->parent.connected = 1;
return 0;
-
-on_error:
- if (t->session) {
- WinHttpCloseHandle(t->session);
- t->session = NULL;
- }
- return -1;
-#endif
}
-/*
- * The HTTP parser is streaming, so we need to wait until we're in the
- * field handler before we can be sure that we can store the previous
- * value. Right now, we only care about the
- * Content-Type. on_header_{field,value} should be kept generic enough
- * to work for any request.
- */
+static void clear_parser_state(http_subtransport *t)
+{
+ unsigned i;
+ char *entry;
-static const char *typestr = "Content-Type";
+ http_parser_init(&t->parser, HTTP_RESPONSE);
+ gitno_buffer_setup(&t->socket,
+ &t->parse_buffer,
+ t->parse_buffer_data,
+ sizeof(t->parse_buffer_data));
-static int on_header_field(http_parser *parser, const char *str, size_t len)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
+ t->last_cb = NONE;
+ t->parse_error = 0;
+ t->parse_finished = 0;
- if (t->last_cb == VALUE && t->ct_found) {
- t->ct_finished = 1;
- t->ct_found = 0;
- t->content_type = git__strdup(git_buf_cstr(buf));
- GITERR_CHECK_ALLOC(t->content_type);
- git_buf_clear(buf);
- }
+ git_buf_free(&t->parse_header_name);
+ git_buf_init(&t->parse_header_name, 0);
- if (t->ct_found) {
- t->last_cb = FIELD;
- return 0;
- }
+ git_buf_free(&t->parse_header_value);
+ git_buf_init(&t->parse_header_value, 0);
- if (t->last_cb != FIELD)
- git_buf_clear(buf);
+ git__free(t->content_type);
+ t->content_type = NULL;
- git_buf_put(buf, str, len);
- t->last_cb = FIELD;
+ git_vector_foreach(&t->www_authenticate, i, entry)
+ git__free(entry);
- return git_buf_oom(buf);
+ git_vector_free(&t->www_authenticate);
}
-static int on_header_value(http_parser *parser, const char *str, size_t len)
+static int http_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf request = GIT_BUF_INIT;
+ parser_context ctx;
- if (t->ct_finished) {
- t->last_cb = VALUE;
- return 0;
- }
+replay:
+ *bytes_read = 0;
- if (t->last_cb == VALUE)
- git_buf_put(buf, str, len);
+ assert(t->connected);
- if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
- t->ct_found = 1;
- git_buf_clear(buf);
- git_buf_put(buf, str, len);
- }
+ if (!s->sent_request) {
+ clear_parser_state(t);
- t->last_cb = VALUE;
+ if (gen_request(&request, t->path, t->host,
+ t->cred, t->auth_mechanism, s->verb,
+ s->service, s->service_url, 0) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
- return git_buf_oom(buf);
-}
+ if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) {
+ git_buf_free(&request);
+ return -1;
+ }
-static int on_headers_complete(http_parser *parser)
-{
- transport_http *t = (transport_http *) parser->data;
- git_buf *buf = &t->buf;
+ git_buf_free(&request);
+ s->sent_request = 1;
+ }
+
+ t->parse_buffer.offset = 0;
- /* The content-type is text/plain for 404, so don't validate */
- if (parser->status_code == 404) {
- git_buf_clear(buf);
+ if (t->parse_finished)
return 0;
- }
- if (t->content_type == NULL) {
- t->content_type = git__strdup(git_buf_cstr(buf));
- if (t->content_type == NULL)
- return t->error = -1;
- }
+ if (gitno_recv(&t->parse_buffer) < 0)
+ return -1;
- git_buf_clear(buf);
- git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
- if (git_buf_oom(buf))
- return t->error = -1;
+ /* 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;
+ }
- if (strcmp(t->content_type, git_buf_cstr(buf)))
- return t->error = -1;
+ if (t->parse_error < 0)
+ return -1;
- git_buf_clear(buf);
return 0;
}
-static int on_message_complete(http_parser *parser)
+static int http_stream_write(
+ git_smart_subtransport_stream *stream,
+ const char *buffer,
+ size_t len)
{
- transport_http *t = (transport_http *) parser->data;
+ http_stream *s = (http_stream *)stream;
+ http_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf request = GIT_BUF_INIT;
- t->transfer_finished = 1;
+ assert(t->connected);
- if (parser->status_code == 404) {
- giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf));
- t->error = -1;
- }
+ /* 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);
- return 0;
-}
+ if (!s->sent_request) {
+ clear_parser_state(t);
-static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
-{
- git_transport *transport = (git_transport *) parser->data;
- transport_http *t = (transport_http *) parser->data;
- gitno_buffer *buf = &transport->buffer;
+ if (gen_request(&request, t->path, t->host,
+ t->cred, t->auth_mechanism, s->verb,
+ s->service, s->service_url, len) < 0) {
+ giterr_set(GITERR_NET, "Failed to generate request");
+ return -1;
+ }
- if (buf->len - buf->offset < len) {
- giterr_set(GITERR_NET, "Can't fit data in the buffer");
- return t->error = -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;
- memcpy(buf->data + buf->offset, str, len);
- buf->offset += len;
+ git_buf_free(&request);
+ s->sent_request = 1;
+ }
return 0;
+
+on_error:
+ git_buf_free(&request);
+ return -1;
}
-static int http_recv_cb(gitno_buffer *buf)
+static void http_stream_free(git_smart_subtransport_stream *stream)
{
- git_transport *transport = (git_transport *) buf->cb_data;
- transport_http *t = (transport_http *) transport;
- size_t old_len;
- char buffer[2048];
-#ifdef GIT_WINHTTP
- DWORD recvd;
-#else
- gitno_buffer inner;
- int error;
-#endif
-
- if (t->transfer_finished)
- return 0;
+ http_stream *s = (http_stream *)stream;
-#ifndef GIT_WINHTTP
- gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer));
+ git__free(s);
+}
- if ((error = gitno_recv(&inner)) < 0)
- return -1;
+static int http_stream_alloc(http_subtransport *t,
+ git_smart_subtransport_stream **stream)
+{
+ http_stream *s;
- old_len = buf->offset;
- http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset);
- if (t->error < 0)
- return t->error;
-#else
- old_len = buf->offset;
- if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) {
- giterr_set(GITERR_OS, "Failed to read data from the network");
- return t->error = -1;
- }
+ if (!stream)
+ return -1;
- if (buf->len - buf->offset < recvd) {
- giterr_set(GITERR_NET, "Can't fit data in the buffer");
- return t->error = -1;
- }
+ s = (http_stream *)git__calloc(sizeof(http_stream), 1);
+ GITERR_CHECK_ALLOC(s);
- memcpy(buf->data + buf->offset, buffer, recvd);
- buf->offset += recvd;
-#endif
+ s->parent.subtransport = &t->parent;
+ s->parent.read = http_stream_read;
+ s->parent.write = http_stream_write;
+ s->parent.free = http_stream_free;
- return (int)(buf->offset - old_len);
+ *stream = (git_smart_subtransport_stream *)s;
+ return 0;
}
-/* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */
-static void setup_gitno_buffer(git_transport *transport)
+static int http_uploadpack_ls(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
{
- transport_http *t = (transport_http *) transport;
+ http_stream *s;
- /* WinHTTP takes care of this for us */
-#ifndef GIT_WINHTTP
- http_parser_init(&t->parser, HTTP_RESPONSE);
- t->parser.data = t;
- t->transfer_finished = 0;
- memset(&t->settings, 0x0, sizeof(http_parser_settings));
- t->settings.on_header_field = on_header_field;
- t->settings.on_header_value = on_header_value;
- t->settings.on_headers_complete = on_headers_complete;
- t->settings.on_body = on_body_fill_buffer;
- t->settings.on_message_complete = on_message_complete;
-#endif
+ if (http_stream_alloc(t, stream) < 0)
+ return -1;
+
+ s = (http_stream *)*stream;
- gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t);
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_ls_service_url;
+ s->verb = get_verb;
+
+ return 0;
}
-static int http_connect(git_transport *transport, int direction)
+static int http_uploadpack(
+ http_subtransport *t,
+ git_smart_subtransport_stream **stream)
{
- transport_http *t = (transport_http *) transport;
- int ret;
- git_buf request = GIT_BUF_INIT;
- const char *service = "upload-pack";
- const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://";
- const char *default_port;
- git_pkt *pkt;
+ http_stream *s;
- if (direction == GIT_DIR_PUSH) {
- giterr_set(GITERR_NET, "Pushing over HTTP is not implemented");
+ if (http_stream_alloc(t, stream) < 0)
return -1;
- }
-
- t->parent.direction = direction;
- if (!git__prefixcmp(url, prefix_http)) {
- url = t->parent.url + strlen(prefix_http);
- default_port = "80";
- }
+ s = (http_stream *)*stream;
- if (!git__prefixcmp(url, prefix_https)) {
- url += strlen(prefix_https);
- default_port = "443";
- }
+ s->service = upload_pack_service;
+ s->service_url = upload_pack_service_url;
+ s->verb = post_verb;
- t->path = strchr(url, '/');
+ return 0;
+}
- if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
- goto cleanup;
+static int http_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *smart_transport,
+ const char *url,
+ git_smart_service_t action)
+{
+ http_subtransport *t = (http_subtransport *)smart_transport;
+ const char *default_port;
+ int flags = 0, ret;
- t->service = git__strdup(service);
- GITERR_CHECK_ALLOC(t->service);
+ if (!stream)
+ return -1;
- if ((ret = do_connect(t)) < 0)
- goto cleanup;
+ if (!t->host || !t->port || !t->path) {
+ if (!git__prefixcmp(url, prefix_http)) {
+ url = url + strlen(prefix_http);
+ default_port = "80";
+ }
- if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0)
- goto cleanup;
+ if (!git__prefixcmp(url, prefix_https)) {
+ url += strlen(prefix_https);
+ default_port = "443";
+ t->use_ssl = 1;
+ }
- setup_gitno_buffer(transport);
- if ((ret = git_protocol_store_refs(transport, 2)) < 0)
- goto cleanup;
+ if ((ret = gitno_extract_host_and_port(&t->host, &t->port,
+ url, default_port)) < 0)
+ return ret;
- pkt = git_vector_get(&transport->refs, 0);
- if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) {
- giterr_set(GITERR_NET, "Invalid HTTP response");
- return t->error = -1;
- } else {
- /* Remove the comment pkt from the list */
- git_vector_remove(&transport->refs, 0);
- git__free(pkt);
+ t->path = strchr(url, '/');
}
- if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
- return t->error = -1;
+ if (!t->connected || !http_should_keep_alive(&t->parser)) {
+ if (t->use_ssl) {
+ int transport_flags;
-cleanup:
- git_buf_free(&request);
- git_buf_clear(&t->buf);
+ if (t->owner->parent.read_flags(&t->owner->parent, &transport_flags) < 0)
+ return -1;
- return ret;
-}
+ flags |= GITNO_CONNECT_SSL;
-static int http_negotiation_step(struct git_transport *transport, void *data, size_t len)
-{
- transport_http *t = (transport_http *) transport;
- int ret;
+ if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & transport_flags)
+ flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT;
+ }
- /* First, send the data as a HTTP POST request */
- if ((ret = do_connect(t)) < 0)
- return -1;
+ if (gitno_connect(&t->socket, t->host, t->port, flags) < 0)
+ return -1;
- if (send_request(t, "upload-pack", data, len, 0) < 0)
- return -1;
-
- /* Then we need to set up the buffer to grab data from the HTTP response */
- setup_gitno_buffer(transport);
+ t->connected = 1;
+ }
- return 0;
-}
+ t->parse_finished = 0;
+ t->parse_error = 0;
-static int http_close(git_transport *transport)
-{
-#ifndef GIT_WINHTTP
- if (gitno_ssl_teardown(transport) < 0)
- return -1;
+ switch (action)
+ {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return http_uploadpack_ls(t, stream);
- if (gitno_close(transport->socket) < 0) {
- giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno));
- return -1;
+ case GIT_SERVICE_UPLOADPACK:
+ return http_uploadpack(t, stream);
}
-#else
- transport_http *t = (transport_http *) transport;
- if (t->request)
- WinHttpCloseHandle(t->request);
- if (t->connection)
- WinHttpCloseHandle(t->connection);
- if (t->session)
- WinHttpCloseHandle(t->session);
-#endif
+ *stream = NULL;
+ return -1;
+}
- transport->connected = 0;
+static void http_free(git_smart_subtransport *smart_transport)
+{
+ http_subtransport *t = (http_subtransport *) smart_transport;
- return 0;
-}
+ clear_parser_state(t);
+ if (t->socket.socket)
+ gitno_close(&t->socket);
-static void http_free(git_transport *transport)
-{
- transport_http *t = (transport_http *) transport;
- git_vector *refs = &transport->refs;
- git_vector *common = &transport->common;
- unsigned int i;
- git_pkt *p;
-
-#ifdef GIT_WIN32
- /* cleanup the WSA context. note that this context
- * can be initialized more than once with WSAStartup(),
- * and needs to be cleaned one time for each init call
- */
- WSACleanup();
-#endif
-
- git_vector_foreach(refs, i, p) {
- git_pkt_free(p);
- }
- git_vector_free(refs);
- git_vector_foreach(common, i, p) {
- git_pkt_free(p);
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
}
- git_vector_free(common);
- git_buf_free(&t->buf);
- git__free(t->content_type);
+
git__free(t->host);
git__free(t->port);
- git__free(t->service);
- git__free(t->parent.url);
git__free(t);
}
-int git_transport_http(git_transport **out)
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
{
- transport_http *t;
+ http_subtransport *t;
- t = git__malloc(sizeof(transport_http));
- GITERR_CHECK_ALLOC(t);
+ if (!out)
+ return -1;
- memset(t, 0x0, sizeof(transport_http));
+ t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
- t->parent.connect = http_connect;
- t->parent.negotiation_step = http_negotiation_step;
- t->parent.close = http_close;
+ t->owner = (transport_smart *)owner;
+ t->parent.action = http_action;
t->parent.free = http_free;
- t->parent.rpc = 1;
-
- if (git_vector_init(&t->parent.refs, 16, NULL) < 0) {
- git__free(t);
- return -1;
- }
-#ifdef GIT_WIN32
- /* on win32, the WSA context needs to be initialized
- * before any socket calls can be performed */
- if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {
- http_free((git_transport *) t);
- giterr_set(GITERR_OS, "Winsock init failed");
- return -1;
- }
-#endif
+ t->settings.on_header_field = on_header_field;
+ t->settings.on_header_value = on_header_value;
+ t->settings.on_headers_complete = on_headers_complete;
+ t->settings.on_body = on_body_fill_buffer;
+ t->settings.on_message_complete = on_message_complete;
- *out = (git_transport *) t;
+ *out = (git_smart_subtransport *) t;
return 0;
}
-int git_transport_https(git_transport **out)
-{
-#if defined(GIT_SSL) || defined(GIT_WINHTTP)
- transport_http *t;
- if (git_transport_http((git_transport **)&t) < 0)
- return -1;
-
- t->parent.use_ssl = 1;
- t->parent.check_cert = 1;
- *out = (git_transport *) t;
-
- return 0;
-#else
- GIT_UNUSED(out);
-
- giterr_set(GITERR_NET, "HTTPS support not available");
- return -1;
-#endif
-}
+#endif /* !GIT_WINHTTP */
diff --git a/src/transports/local.c b/src/transports/local.c
index 561c84fa0..46c9218c7 100644
--- a/src/transports/local.c
+++ b/src/transports/local.c
@@ -10,16 +10,29 @@
#include "git2/repository.h"
#include "git2/object.h"
#include "git2/tag.h"
+#include "git2/transport.h"
+#include "git2/revwalk.h"
+#include "git2/odb_backend.h"
+#include "git2/pack.h"
+#include "git2/commit.h"
+#include "git2/revparse.h"
+#include "pack-objects.h"
#include "refs.h"
-#include "transport.h"
#include "posix.h"
#include "path.h"
#include "buffer.h"
-#include "pkt.h"
+#include "repository.h"
+#include "odb.h"
typedef struct {
git_transport parent;
+ char *url;
+ int direction;
+ int flags;
+ git_atomic cancelled;
git_repository *repo;
+ git_vector refs;
+ unsigned connected : 1;
} transport_local;
static int add_ref(transport_local *t, const char *name)
@@ -27,32 +40,24 @@ static int add_ref(transport_local *t, const char *name)
const char peeled[] = "^{}";
git_remote_head *head;
git_object *obj = NULL, *target = NULL;
- git_transport *transport = (git_transport *) t;
git_buf buf = GIT_BUF_INIT;
- git_pkt_ref *pkt;
- head = git__malloc(sizeof(git_remote_head));
+ head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
- pkt = git__malloc(sizeof(git_pkt_ref));
- GITERR_CHECK_ALLOC(pkt);
head->name = git__strdup(name);
GITERR_CHECK_ALLOC(head->name);
if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0) {
+ git__free(head->name);
git__free(head);
- git__free(pkt->head.name);
- git__free(pkt);
+ return -1;
}
- pkt->type = GIT_PKT_REF;
- memcpy(&pkt->head, head, sizeof(git_remote_head));
- git__free(head);
-
- if (git_vector_insert(&transport->refs, pkt) < 0)
+ if (git_vector_insert(&t->refs, head) < 0)
{
- git__free(pkt->head.name);
- git__free(pkt);
+ git__free(head->name);
+ git__free(head);
return -1;
}
@@ -60,7 +65,7 @@ static int add_ref(transport_local *t, const char *name)
if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
return 0;
- if (git_object_lookup(&obj, t->repo, &pkt->head.oid, GIT_OBJ_ANY) < 0)
+ if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0)
return -1;
head = NULL;
@@ -72,27 +77,21 @@ static int add_ref(transport_local *t, const char *name)
}
/* And if it's a tag, peel it, and add it to the list */
- head = git__malloc(sizeof(git_remote_head));
+ head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head));
GITERR_CHECK_ALLOC(head);
if (git_buf_join(&buf, 0, name, peeled) < 0)
return -1;
head->name = git_buf_detach(&buf);
- pkt = git__malloc(sizeof(git_pkt_ref));
- GITERR_CHECK_ALLOC(pkt);
- pkt->type = GIT_PKT_REF;
-
if (git_tag_peel(&target, (git_tag *) obj) < 0)
goto on_error;
git_oid_cpy(&head->oid, git_object_id(target));
git_object_free(obj);
git_object_free(target);
- memcpy(&pkt->head, head, sizeof(git_remote_head));
- git__free(head);
- if (git_vector_insert(&transport->refs, pkt) < 0)
+ if (git_vector_insert(&t->refs, head) < 0)
return -1;
return 0;
@@ -107,12 +106,11 @@ static int store_refs(transport_local *t)
{
unsigned int i;
git_strarray ref_names = {0};
- git_transport *transport = (git_transport *) t;
assert(t);
if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
- git_vector_init(&transport->refs, (unsigned int)ref_names.count, NULL) < 0)
+ git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)
goto on_error;
/* Sort the references first */
@@ -131,7 +129,7 @@ static int store_refs(transport_local *t)
return 0;
on_error:
- git_vector_free(&transport->refs);
+ git_vector_free(&t->refs);
git_strarray_free(&ref_names);
return -1;
}
@@ -140,7 +138,11 @@ on_error:
* Try to open the url as a git directory. The direction doesn't
* matter in this case because we're calulating the heads ourselves.
*/
-static int local_connect(git_transport *transport, int direction)
+static int local_connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ int direction, int flags)
{
git_repository *repo;
int error;
@@ -148,18 +150,23 @@ static int local_connect(git_transport *transport, int direction)
const char *path;
git_buf buf = GIT_BUF_INIT;
- GIT_UNUSED(direction);
+ GIT_UNUSED(cred_acquire_cb);
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+ t->direction = direction;
+ t->flags = flags;
/* The repo layer doesn't want the prefix */
- if (!git__prefixcmp(transport->url, "file://")) {
- if (git_path_fromurl(&buf, transport->url) < 0) {
+ if (!git__prefixcmp(t->url, "file://")) {
+ if (git_path_fromurl(&buf, t->url) < 0) {
git_buf_free(&buf);
return -1;
}
path = git_buf_cstr(&buf);
} else { /* We assume transport->url is already a path */
- path = transport->url;
+ path = t->url;
}
error = git_repository_open(&repo, path);
@@ -174,26 +181,194 @@ static int local_connect(git_transport *transport, int direction)
if (store_refs(t) < 0)
return -1;
- t->parent.connected = 1;
+ t->connected = 1;
return 0;
}
-static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
+static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
{
- GIT_UNUSED(transport);
- GIT_UNUSED(repo);
- GIT_UNUSED(wants);
+ transport_local *t = (transport_local *)transport;
+ unsigned int i;
+ git_remote_head *head = NULL;
- giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry");
- return -1;
+ if (!t->connected) {
+ giterr_set(GITERR_NET, "The transport is not connected");
+ return -1;
+ }
+
+ git_vector_foreach(&t->refs, i, head) {
+ if (list_cb(head, payload))
+ return GIT_EUSER;
+ }
+
+ return 0;
+}
+
+static int local_negotiate_fetch(
+ git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count)
+{
+ transport_local *t = (transport_local*)transport;
+ git_remote_head *rhead;
+ unsigned int i;
+
+ GIT_UNUSED(refs);
+ GIT_UNUSED(count);
+
+ /* Fill in the loids */
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+
+ int error = git_revparse_single(&obj, repo, rhead->name);
+ if (!error)
+ git_oid_cpy(&rhead->loid, git_object_id(obj));
+ else if (error != GIT_ENOTFOUND)
+ return error;
+ git_object_free(obj);
+ giterr_clear();
+ }
+
+ return 0;
+}
+
+typedef struct foreach_data {
+ git_transfer_progress *stats;
+ git_transfer_progress_callback progress_cb;
+ void *progress_payload;
+ git_odb_writepack *writepack;
+} foreach_data;
+
+static int foreach_cb(void *buf, size_t len, void *payload)
+{
+ foreach_data *data = (foreach_data*)payload;
+
+ data->stats->received_bytes += len;
+ return data->writepack->add(data->writepack, buf, len, data->stats);
+}
+
+static int local_download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ transport_local *t = (transport_local*)transport;
+ git_revwalk *walk = NULL;
+ git_remote_head *rhead;
+ unsigned int i;
+ int error = -1;
+ git_oid oid;
+ git_packbuilder *pack = NULL;
+ git_odb_writepack *writepack = NULL;
+ git_odb *odb = NULL;
+
+ if ((error = git_revwalk_new(&walk, t->repo)) < 0)
+ goto cleanup;
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
+ goto cleanup;
+
+ stats->total_objects = 0;
+ stats->indexed_objects = 0;
+ stats->received_objects = 0;
+ stats->received_bytes = 0;
+
+ git_vector_foreach(&t->refs, i, rhead) {
+ git_object *obj;
+ if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
+ goto cleanup;
+
+ if (git_object_type(obj) == GIT_OBJ_COMMIT) {
+ /* Revwalker includes only wanted commits */
+ error = git_revwalk_push(walk, &rhead->oid);
+ if (!git_oid_iszero(&rhead->loid))
+ error = git_revwalk_hide(walk, &rhead->loid);
+ } else {
+ /* Tag or some other wanted object. Add it on its own */
+ error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);
+ }
+ git_object_free(obj);
+ }
+
+ /* Walk the objects, building a packfile */
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
+ goto cleanup;
+
+ while ((error = git_revwalk_next(&oid, walk)) == 0) {
+ git_commit *commit;
+
+ /* Skip commits we already have */
+ if (git_odb_exists(odb, &oid)) continue;
+
+ if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {
+ const git_oid *tree_oid = git_commit_tree_oid(commit);
+ git_commit_free(commit);
+
+ /* Add the commit and its tree */
+ if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||
+ (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0)
+ goto cleanup;
+ }
+ }
+
+ if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)
+ goto cleanup;
+
+ /* Write the data to the ODB */
+ {
+ foreach_data data = {0};
+ data.stats = stats;
+ data.progress_cb = progress_cb;
+ data.progress_payload = progress_payload;
+ data.writepack = writepack;
+
+ if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) < 0)
+ goto cleanup;
+ }
+ error = writepack->commit(writepack, stats);
+
+cleanup:
+ if (writepack) writepack->free(writepack);
+ git_packbuilder_free(pack);
+ git_revwalk_free(walk);
+ return error;
+}
+
+static int local_is_connected(git_transport *transport, int *connected)
+{
+ transport_local *t = (transport_local *)transport;
+
+ *connected = t->connected;
+
+ return 0;
+}
+
+static int local_read_flags(git_transport *transport, int *flags)
+{
+ transport_local *t = (transport_local *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static void local_cancel(git_transport *transport)
+{
+ transport_local *t = (transport_local *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
}
static int local_close(git_transport *transport)
{
transport_local *t = (transport_local *)transport;
- t->parent.connected = 0;
+ t->connected = 0;
git_repository_free(t->repo);
t->repo = NULL;
@@ -204,18 +379,18 @@ static void local_free(git_transport *transport)
{
unsigned int i;
transport_local *t = (transport_local *) transport;
- git_vector *vec = &transport->refs;
- git_pkt_ref *pkt;
+ git_vector *vec = &t->refs;
+ git_remote_head *head;
assert(transport);
- git_vector_foreach (vec, i, pkt) {
- git__free(pkt->head.name);
- git__free(pkt);
+ git_vector_foreach (vec, i, head) {
+ git__free(head->name);
+ git__free(head);
}
git_vector_free(vec);
- git__free(t->parent.url);
+ git__free(t->url);
git__free(t);
}
@@ -223,20 +398,26 @@ static void local_free(git_transport *transport)
* Public API *
**************/
-int git_transport_local(git_transport **out)
+int git_transport_local(git_transport **out, void *param)
{
transport_local *t;
+ GIT_UNUSED(param);
+
t = git__malloc(sizeof(transport_local));
GITERR_CHECK_ALLOC(t);
memset(t, 0x0, sizeof(transport_local));
-
- t->parent.own_logic = 1;
+
t->parent.connect = local_connect;
t->parent.negotiate_fetch = local_negotiate_fetch;
+ t->parent.download_pack = local_download_pack;
t->parent.close = local_close;
t->parent.free = local_free;
+ t->parent.ls = local_ls;
+ t->parent.is_connected = local_is_connected;
+ t->parent.read_flags = local_read_flags;
+ t->parent.cancel = local_cancel;
*out = (git_transport *) t;
diff --git a/src/transports/smart.c b/src/transports/smart.c
new file mode 100644
index 000000000..8f9715a3f
--- /dev/null
+++ b/src/transports/smart.c
@@ -0,0 +1,284 @@
+/*
+ * 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 "smart.h"
+#include "refs.h"
+
+static int git_smart__recv_cb(gitno_buffer *buf)
+{
+ transport_smart *t = (transport_smart *) buf->cb_data;
+ size_t old_len, bytes_read;
+ int error;
+
+ assert(t->current_stream);
+
+ old_len = buf->offset;
+
+ if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
+ return error;
+
+ buf->offset += bytes_read;
+
+ if (t->packetsize_cb)
+ t->packetsize_cb(bytes_read, t->packetsize_payload);
+
+ return (int)(buf->offset - old_len);
+}
+
+GIT_INLINE(void) git_smart__reset_stream(transport_smart *t)
+{
+ if (t->current_stream) {
+ t->current_stream->free(t->current_stream);
+ t->current_stream = NULL;
+ }
+}
+
+static int git_smart__set_callbacks(
+ git_transport *transport,
+ git_transport_message_cb progress_cb,
+ git_transport_message_cb error_cb,
+ void *message_cb_payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ t->progress_cb = progress_cb;
+ t->error_cb = error_cb;
+ t->message_cb_payload = message_cb_payload;
+
+ return 0;
+}
+
+static int git_smart__connect(
+ git_transport *transport,
+ const char *url,
+ git_cred_acquire_cb cred_acquire_cb,
+ int direction,
+ int flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+ git_pkt *pkt;
+
+ git_smart__reset_stream(t);
+
+ t->url = git__strdup(url);
+ GITERR_CHECK_ALLOC(t->url);
+
+ t->direction = direction;
+ t->flags = flags;
+ t->cred_acquire_cb = cred_acquire_cb;
+
+ if (GIT_DIR_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);
+ }
+ }
+
+ /* We now have loaded the refs. */
+ t->have_refs = 1;
+
+ if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0)
+ return -1;
+
+ if (t->rpc)
+ git_smart__reset_stream(t);
+
+ /* We're now logically connected. */
+ t->connected = 1;
+
+ return 0;
+ }
+ else
+ {
+ giterr_set(GITERR_NET, "Push not implemented");
+ return -1;
+ }
+
+ return -1;
+}
+
+static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+ unsigned int i;
+ git_pkt *p = NULL;
+
+ if (!t->have_refs) {
+ giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
+ return -1;
+ }
+
+ git_vector_foreach(&t->refs, i, p) {
+ git_pkt_ref *pkt = NULL;
+
+ if (p->type != GIT_PKT_REF)
+ continue;
+
+ pkt = (git_pkt_ref *)p;
+
+ if (list_cb(&pkt->head, payload))
+ return GIT_EUSER;
+ }
+
+ return 0;
+}
+
+int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
+{
+ transport_smart *t = (transport_smart *)transport;
+ git_smart_subtransport_stream *stream;
+ int error;
+
+ if (t->rpc)
+ git_smart__reset_stream(t);
+
+ if (GIT_DIR_FETCH == t->direction) {
+ if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
+ return error;
+
+ /* If this is a stateful implementation, the stream we get back should be the same */
+ assert(t->rpc || t->current_stream == stream);
+
+ /* Save off the current stream (i.e. socket) that we are working with */
+ t->current_stream = stream;
+
+ if ((error = stream->write(stream, (const char *)data, len)) < 0)
+ return error;
+
+ gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
+
+ return 0;
+ }
+
+ giterr_set(GITERR_NET, "Push not implemented");
+ return -1;
+}
+
+static void git_smart__cancel(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ git_atomic_set(&t->cancelled, 1);
+}
+
+static int git_smart__is_connected(git_transport *transport, int *connected)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ *connected = t->connected;
+
+ return 0;
+}
+
+static int git_smart__read_flags(git_transport *transport, int *flags)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ *flags = t->flags;
+
+ return 0;
+}
+
+static int git_smart__close(git_transport *transport)
+{
+ transport_smart *t = (transport_smart *)transport;
+
+ git_smart__reset_stream(t);
+
+ t->connected = 0;
+
+ return 0;
+}
+
+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);
+
+ /* 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)
+{
+ transport_smart *t;
+ git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
+
+ if (!param)
+ return -1;
+
+ t = (transport_smart *)git__calloc(sizeof(transport_smart), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->parent.set_callbacks = git_smart__set_callbacks;
+ t->parent.connect = git_smart__connect;
+ t->parent.close = git_smart__close;
+ t->parent.free = git_smart__free;
+ t->parent.negotiate_fetch = git_smart__negotiate_fetch;
+ t->parent.download_pack = git_smart__download_pack;
+ t->parent.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->rpc = definition->rpc;
+
+ if (git_vector_init(&t->refs, 16, NULL) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ if (definition->callback(&t->wrapped, &t->parent) < 0) {
+ git__free(t);
+ return -1;
+ }
+
+ *out = (git_transport *) t;
+ return 0;
+}
diff --git a/src/transports/smart.h b/src/transports/smart.h
new file mode 100644
index 000000000..046bc89a4
--- /dev/null
+++ b/src/transports/smart.h
@@ -0,0 +1,150 @@
+/*
+ * 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 "vector.h"
+#include "netops.h"
+#include "buffer.h"
+
+#define GIT_SIDE_BAND_DATA 1
+#define GIT_SIDE_BAND_PROGRESS 2
+#define GIT_SIDE_BAND_ERROR 3
+
+#define GIT_CAP_OFS_DELTA "ofs-delta"
+#define GIT_CAP_MULTI_ACK "multi_ack"
+#define GIT_CAP_SIDE_BAND "side-band"
+#define GIT_CAP_SIDE_BAND_64K "side-band-64k"
+#define GIT_CAP_INCLUDE_TAG "include-tag"
+
+enum git_pkt_type {
+ GIT_PKT_CMD,
+ GIT_PKT_FLUSH,
+ GIT_PKT_REF,
+ GIT_PKT_HAVE,
+ GIT_PKT_ACK,
+ GIT_PKT_NAK,
+ GIT_PKT_PACK,
+ GIT_PKT_COMMENT,
+ GIT_PKT_ERR,
+ GIT_PKT_DATA,
+ GIT_PKT_PROGRESS,
+};
+
+/* Used for multi-ack */
+enum git_ack_status {
+ GIT_ACK_NONE,
+ GIT_ACK_CONTINUE,
+ GIT_ACK_COMMON,
+ GIT_ACK_READY
+};
+
+/* This would be a flush pkt */
+typedef struct {
+ enum git_pkt_type type;
+} git_pkt;
+
+struct git_pkt_cmd {
+ enum git_pkt_type type;
+ char *cmd;
+ char *path;
+ char *host;
+};
+
+/* This is a pkt-line with some info in it */
+typedef struct {
+ enum git_pkt_type type;
+ git_remote_head head;
+ char *capabilities;
+} git_pkt_ref;
+
+/* Useful later */
+typedef struct {
+ enum git_pkt_type type;
+ git_oid oid;
+ enum git_ack_status status;
+} git_pkt_ack;
+
+typedef struct {
+ enum git_pkt_type type;
+ char comment[GIT_FLEX_ARRAY];
+} git_pkt_comment;
+
+typedef struct {
+ enum git_pkt_type type;
+ int len;
+ char data[GIT_FLEX_ARRAY];
+} git_pkt_data;
+
+typedef git_pkt_data git_pkt_progress;
+
+typedef struct {
+ enum git_pkt_type type;
+ char error[GIT_FLEX_ARRAY];
+} git_pkt_err;
+
+typedef struct transport_smart_caps {
+ int common:1,
+ ofs_delta:1,
+ multi_ack: 1,
+ side_band:1,
+ side_band_64k:1,
+ include_tag:1;
+} transport_smart_caps;
+
+typedef void (*packetsize_cb)(int received, void *payload);
+
+typedef struct {
+ git_transport parent;
+ char *url;
+ git_cred_acquire_cb cred_acquire_cb;
+ int direction;
+ int flags;
+ git_transport_message_cb progress_cb;
+ git_transport_message_cb error_cb;
+ void *message_cb_payload;
+ git_smart_subtransport *wrapped;
+ git_smart_subtransport_stream *current_stream;
+ transport_smart_caps caps;
+ git_vector refs;
+ git_vector common;
+ git_atomic cancelled;
+ packetsize_cb packetsize_cb;
+ void *packetsize_payload;
+ unsigned rpc : 1,
+ have_refs : 1,
+ connected : 1;
+ gitno_buffer buffer;
+ char buffer_data[65536];
+} transport_smart;
+
+/* 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__negotiate_fetch(
+ git_transport *transport,
+ git_repository *repo,
+ const git_remote_head * const *refs,
+ size_t count);
+
+int git_smart__download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload);
+
+/* smart.c */
+int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
+
+/* smart_pkt.c */
+int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
+int git_pkt_buffer_flush(git_buf *buf);
+int git_pkt_send_flush(GIT_SOCKET s);
+int git_pkt_buffer_done(git_buf *buf);
+int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_buf *buf);
+int git_pkt_buffer_have(git_oid *oid, git_buf *buf);
+void git_pkt_free(git_pkt *pkt);
diff --git a/src/pkt.c b/src/transports/smart_pkt.c
index 91f9b65c2..26fc0e4aa 100644
--- a/src/pkt.c
+++ b/src/transports/smart_pkt.c
@@ -12,12 +12,11 @@
#include "git2/refs.h"
#include "git2/revwalk.h"
-#include "pkt.h"
+#include "smart.h"
#include "util.h"
#include "netops.h"
#include "posix.h"
#include "buffer.h"
-#include "protocol.h"
#include <ctype.h>
@@ -335,7 +334,7 @@ int git_pkt_buffer_flush(git_buf *buf)
return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str));
}
-static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf)
+static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf)
{
git_buf str = GIT_BUF_INIT;
char oid[GIT_OID_HEXSZ +1] = {0};
@@ -376,28 +375,32 @@ static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps
* is overwrite the OID each time.
*/
-int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf)
+int git_pkt_buffer_wants(
+ const git_remote_head * const *refs,
+ size_t count,
+ transport_smart_caps *caps,
+ git_buf *buf)
{
- unsigned int i = 0;
- git_remote_head *head;
+ size_t i = 0;
+ const git_remote_head *head;
if (caps->common) {
- for (; i < refs->length; ++i) {
- head = refs->contents[i];
+ for (; i < count; ++i) {
+ head = refs[i];
if (!head->local)
break;
}
- if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0)
+ if (buffer_want_with_caps(refs[i], caps, buf) < 0)
return -1;
i++;
}
- for (; i < refs->length; ++i) {
+ for (; i < count; ++i) {
char oid[GIT_OID_HEXSZ];
- head = refs->contents[i];
+ head = refs[i];
if (head->local)
continue;
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
new file mode 100644
index 000000000..e24eb2783
--- /dev/null
+++ b/src/transports/smart_protocol.c
@@ -0,0 +1,481 @@
+/*
+ * 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 "smart.h"
+#include "refs.h"
+#include "repository.h"
+
+#define NETWORK_XFER_THRESHOLD (100*1024)
+
+int git_smart__store_refs(transport_smart *t, int flushes)
+{
+ gitno_buffer *buf = &t->buffer;
+ git_vector *refs = &t->refs;
+ int error, flush = 0, recvd;
+ const char *line_end;
+ git_pkt *pkt;
+
+ do {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error < 0 && error != GIT_EBUFS)
+ return -1;
+
+ if (error == GIT_EBUFS) {
+ if ((recvd = gitno_recv(buf)) < 0)
+ return -1;
+
+ if (recvd == 0 && !flush) {
+ giterr_set(GITERR_NET, "Early EOF");
+ return -1;
+ }
+
+ continue;
+ }
+
+ gitno_consume(buf, line_end);
+ if (pkt->type == GIT_PKT_ERR) {
+ giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error);
+ git__free(pkt);
+ return -1;
+ }
+
+ if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0)
+ return -1;
+
+ if (pkt->type == GIT_PKT_FLUSH) {
+ flush++;
+ git_pkt_free(pkt);
+ }
+ } while (flush < flushes);
+
+ return flush;
+}
+
+int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
+{
+ const char *ptr;
+
+ /* No refs or capabilites, odd but not a problem */
+ if (pkt == NULL || pkt->capabilities == NULL)
+ return 0;
+
+ ptr = pkt->capabilities;
+ while (ptr != NULL && *ptr != '\0') {
+ if (*ptr == ' ')
+ ptr++;
+
+ if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
+ caps->common = caps->ofs_delta = 1;
+ ptr += strlen(GIT_CAP_OFS_DELTA);
+ continue;
+ }
+
+ if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
+ caps->common = caps->multi_ack = 1;
+ ptr += strlen(GIT_CAP_MULTI_ACK);
+ continue;
+ }
+
+ if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) {
+ caps->common = caps->include_tag = 1;
+ ptr += strlen(GIT_CAP_INCLUDE_TAG);
+ continue;
+ }
+
+ /* Keep side-band check after side-band-64k */
+ if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) {
+ caps->common = caps->side_band_64k = 1;
+ ptr += strlen(GIT_CAP_SIDE_BAND_64K);
+ continue;
+ }
+
+ if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) {
+ caps->common = caps->side_band = 1;
+ ptr += strlen(GIT_CAP_SIDE_BAND);
+ continue;
+ }
+
+ /* We don't know this capability, so skip it */
+ ptr = strchr(ptr, ' ');
+ }
+
+ return 0;
+}
+
+static int recv_pkt(git_pkt **out, gitno_buffer *buf)
+{
+ const char *ptr = buf->data, *line_end = ptr;
+ git_pkt *pkt;
+ int pkt_type, error = 0, ret;
+
+ do {
+ if (buf->offset > 0)
+ error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
+ else
+ error = GIT_EBUFS;
+
+ if (error == 0)
+ break; /* return the pkt */
+
+ if (error < 0 && error != GIT_EBUFS)
+ return -1;
+
+ if ((ret = gitno_recv(buf)) < 0)
+ return -1;
+ } while (error);
+
+ gitno_consume(buf, line_end);
+ pkt_type = pkt->type;
+ if (out != NULL)
+ *out = pkt;
+ else
+ git__free(pkt);
+
+ return pkt_type;
+}
+
+static int store_common(transport_smart *t)
+{
+ git_pkt *pkt = NULL;
+ gitno_buffer *buf = &t->buffer;
+
+ do {
+ if (recv_pkt(&pkt, buf) < 0)
+ return -1;
+
+ if (pkt->type == GIT_PKT_ACK) {
+ if (git_vector_insert(&t->common, pkt) < 0)
+ return -1;
+ } else {
+ git__free(pkt);
+ return 0;
+ }
+
+ } while (1);
+
+ return 0;
+}
+
+static int fetch_setup_walk(git_revwalk **out, git_repository *repo)
+{
+ git_revwalk *walk;
+ git_strarray refs;
+ unsigned int i;
+ git_reference *ref;
+
+ if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0)
+ return -1;
+
+ if (git_revwalk_new(&walk, repo) < 0)
+ return -1;
+
+ git_revwalk_sorting(walk, GIT_SORT_TIME);
+
+ for (i = 0; i < refs.count; ++i) {
+ /* No tags */
+ if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
+ continue;
+
+ if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0)
+ goto on_error;
+
+ if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
+ continue;
+ if (git_revwalk_push(walk, git_reference_oid(ref)) < 0)
+ goto on_error;
+
+ git_reference_free(ref);
+ }
+
+ git_strarray_free(&refs);
+ *out = walk;
+ return 0;
+
+on_error:
+ git_reference_free(ref);
+ git_strarray_free(&refs);
+ return -1;
+}
+
+int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count)
+{
+ transport_smart *t = (transport_smart *)transport;
+ gitno_buffer *buf = &t->buffer;
+ git_buf data = GIT_BUF_INIT;
+ git_revwalk *walk = NULL;
+ int error = -1, pkt_type;
+ unsigned int i;
+ git_oid oid;
+
+ /* No own logic, do our thing */
+ if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
+ return -1;
+
+ if (fetch_setup_walk(&walk, repo) < 0)
+ goto on_error;
+ /*
+ * We don't support any kind of ACK extensions, so the negotiation
+ * boils down to sending what we have and listening for an ACK
+ * every once in a while.
+ */
+ i = 0;
+ while ((error = git_revwalk_next(&oid, walk)) == 0) {
+ git_pkt_buffer_have(&oid, &data);
+ i++;
+ if (i % 20 == 0) {
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+
+ git_pkt_buffer_flush(&data);
+ if (git_buf_oom(&data))
+ goto on_error;
+
+ if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0)
+ goto on_error;
+
+ git_buf_clear(&data);
+ if (t->caps.multi_ack) {
+ if (store_common(t) < 0)
+ goto on_error;
+ } else {
+ pkt_type = recv_pkt(NULL, buf);
+
+ if (pkt_type == GIT_PKT_ACK) {
+ break;
+ } else if (pkt_type == GIT_PKT_NAK) {
+ continue;
+ } else {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ goto on_error;
+ }
+ }
+ }
+
+ if (t->common.length > 0)
+ break;
+
+ if (i % 20 == 0 && t->rpc) {
+ git_pkt_ack *pkt;
+ unsigned int i;
+
+ if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, i, pkt) {
+ git_pkt_buffer_have(&pkt->oid, &data);
+ }
+
+ if (git_buf_oom(&data))
+ goto on_error;
+ }
+ }
+
+ if (error < 0 && error != GIT_ITEROVER)
+ goto on_error;
+
+ /* Tell the other end that we're done negotiating */
+ if (t->rpc && t->common.length > 0) {
+ git_pkt_ack *pkt;
+ unsigned int i;
+
+ if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0)
+ goto on_error;
+
+ git_vector_foreach(&t->common, i, pkt) {
+ git_pkt_buffer_have(&pkt->oid, &data);
+ }
+
+ if (git_buf_oom(&data))
+ goto on_error;
+ }
+
+ git_pkt_buffer_done(&data);
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+ if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0)
+ goto on_error;
+
+ git_buf_free(&data);
+ git_revwalk_free(walk);
+
+ /* Now let's eat up whatever the server gives us */
+ if (!t->caps.multi_ack) {
+ pkt_type = recv_pkt(NULL, buf);
+ if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) {
+ giterr_set(GITERR_NET, "Unexpected pkt type");
+ return -1;
+ }
+ } else {
+ git_pkt_ack *pkt;
+ do {
+ if (recv_pkt((git_pkt **)&pkt, buf) < 0)
+ return -1;
+
+ if (pkt->type == GIT_PKT_NAK ||
+ (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {
+ git__free(pkt);
+ break;
+ }
+
+ git__free(pkt);
+ } while (1);
+ }
+
+ return 0;
+
+on_error:
+ git_revwalk_free(walk);
+ git_buf_free(&data);
+ return error;
+}
+
+static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_transfer_progress *stats)
+{
+ int recvd;
+
+ do {
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ return GIT_EUSER;
+ }
+
+ if (writepack->add(writepack, buf->data, buf->offset, stats) < 0)
+ return -1;
+
+ gitno_consume_n(buf, buf->offset);
+
+ if ((recvd = gitno_recv(buf)) < 0)
+ return -1;
+ } while(recvd > 0);
+
+ if (writepack->commit(writepack, stats))
+ return -1;
+
+ return 0;
+}
+
+struct network_packetsize_payload
+{
+ git_transfer_progress_callback callback;
+ void *payload;
+ git_transfer_progress *stats;
+ size_t last_fired_bytes;
+};
+
+static void network_packetsize(int received, void *payload)
+{
+ struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload;
+
+ /* Accumulate bytes */
+ npp->stats->received_bytes += received;
+
+ /* Fire notification if the threshold is reached */
+ if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) {
+ npp->last_fired_bytes = npp->stats->received_bytes;
+ npp->callback(npp->stats, npp->payload);
+ }
+}
+
+int git_smart__download_pack(
+ git_transport *transport,
+ git_repository *repo,
+ git_transfer_progress *stats,
+ git_transfer_progress_callback progress_cb,
+ void *progress_payload)
+{
+ transport_smart *t = (transport_smart *)transport;
+ gitno_buffer *buf = &t->buffer;
+ git_odb *odb;
+ struct git_odb_writepack *writepack = NULL;
+ int error = -1;
+ struct network_packetsize_payload npp = {0};
+
+ memset(stats, 0, sizeof(git_transfer_progress));
+
+ if (progress_cb) {
+ npp.callback = progress_cb;
+ npp.payload = progress_payload;
+ npp.stats = stats;
+ t->packetsize_cb = &network_packetsize;
+ t->packetsize_payload = &npp;
+
+ /* We might have something in the buffer already from negotiate_fetch */
+ if (t->buffer.offset > 0)
+ t->packetsize_cb(t->buffer.offset, t->packetsize_payload);
+ }
+
+ if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
+ ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0))
+ goto on_error;
+
+ /*
+ * If the remote doesn't support the side-band, we can feed
+ * the data directly to the pack writer. Otherwise, we need to
+ * check which one belongs there.
+ */
+ if (!t->caps.side_band && !t->caps.side_band_64k) {
+ if (no_sideband(t, writepack, buf, stats) < 0)
+ goto on_error;
+
+ goto on_success;
+ }
+
+ do {
+ git_pkt *pkt;
+
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto on_error;
+ }
+
+ if (recv_pkt(&pkt, buf) < 0)
+ goto on_error;
+
+ if (pkt->type == GIT_PKT_PROGRESS) {
+ if (t->progress_cb) {
+ git_pkt_progress *p = (git_pkt_progress *) pkt;
+ t->progress_cb(p->data, p->len, t->message_cb_payload);
+ }
+ git__free(pkt);
+ } else if (pkt->type == GIT_PKT_DATA) {
+ git_pkt_data *p = (git_pkt_data *) pkt;
+ if (writepack->add(writepack, p->data, p->len, stats) < 0)
+ goto on_error;
+
+ git__free(pkt);
+ } else if (pkt->type == GIT_PKT_FLUSH) {
+ /* A flush indicates the end of the packfile */
+ git__free(pkt);
+ break;
+ }
+ } while (1);
+
+ if (writepack->commit(writepack, stats) < 0)
+ goto on_error;
+
+on_success:
+ error = 0;
+
+on_error:
+ writepack->free(writepack);
+
+ /* Trailing execution of progress_cb, if necessary */
+ if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes)
+ npp.callback(npp.stats, npp.payload);
+
+ return error;
+}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
new file mode 100644
index 000000000..df6cd87ec
--- /dev/null
+++ b/src/transports/winhttp.c
@@ -0,0 +1,634 @@
+/*
+ * 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.
+ */
+
+#ifdef GIT_WINHTTP
+
+#include "git2.h"
+#include "git2/transport.h"
+#include "buffer.h"
+#include "posix.h"
+#include "netops.h"
+#include "smart.h"
+
+#include <winhttp.h>
+#pragma comment(lib, "winhttp")
+
+#define WIDEN2(s) L ## s
+#define WIDEN(s) WIDEN2(s)
+
+#define MAX_CONTENT_TYPE_LEN 100
+#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109
+
+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 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 int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
+ SECURITY_FLAG_IGNORE_UNKNOWN_CA;
+
+#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport)
+
+typedef enum {
+ GIT_WINHTTP_AUTH_BASIC = 1,
+} winhttp_authmechanism_t;
+
+typedef struct {
+ git_smart_subtransport_stream parent;
+ const char *service;
+ const char *service_url;
+ const wchar_t *verb;
+ HINTERNET request;
+ unsigned sent_request : 1,
+ received_response : 1;
+} winhttp_stream;
+
+typedef struct {
+ git_smart_subtransport parent;
+ transport_smart *owner;
+ const char *path;
+ char *host;
+ char *port;
+ git_cred *cred;
+ int auth_mechanism;
+ HINTERNET session;
+ HINTERNET connection;
+ unsigned use_ssl : 1;
+} winhttp_subtransport;
+
+static int apply_basic_credential(HINTERNET request, git_cred *cred)
+{
+ git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred;
+ git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT;
+ wchar_t *wide = NULL;
+ int error = -1, wide_len;
+
+ git_buf_printf(&raw, "%s:%s", c->username, c->password);
+
+ if (git_buf_oom(&raw) ||
+ git_buf_puts(&buf, "Authorization: Basic ") < 0 ||
+ git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0)
+ goto on_error;
+
+ wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ git_buf_cstr(&buf), -1, NULL, 0);
+
+ if (!wide_len) {
+ giterr_set(GITERR_OS, "Failed to measure string for wide conversion");
+ goto on_error;
+ }
+
+ wide = (wchar_t *)git__malloc(wide_len * sizeof(wchar_t));
+
+ if (!wide)
+ goto on_error;
+
+ if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
+ git_buf_cstr(&buf), -1, wide, wide_len)) {
+ giterr_set(GITERR_OS, "Failed to convert string to wide form");
+ goto on_error;
+ }
+
+ if (!WinHttpAddRequestHeaders(request, wide, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+
+ error = 0;
+
+on_error:
+ /* We were dealing with plaintext passwords, so clean up after ourselves a bit. */
+ if (wide)
+ memset(wide, 0x0, wide_len * sizeof(wchar_t));
+
+ if (buf.size)
+ memset(buf.ptr, 0x0, buf.size);
+
+ if (raw.size)
+ memset(raw.ptr, 0x0, raw.size);
+
+ git__free(wide);
+ git_buf_free(&buf);
+ git_buf_free(&raw);
+ return error;
+}
+
+static int winhttp_stream_connect(winhttp_stream *s)
+{
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ git_buf buf = GIT_BUF_INIT;
+ wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN];
+ wchar_t *types[] = { L"*/*", NULL };
+ BOOL peerdist = FALSE;
+
+ /* Prepare URL */
+ git_buf_printf(&buf, "%s%s", t->path, s->service_url);
+
+ if (git_buf_oom(&buf))
+ return -1;
+
+ git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
+
+ /* Establish request */
+ s->request = WinHttpOpenRequest(
+ t->connection,
+ s->verb,
+ url,
+ NULL,
+ WINHTTP_NO_REFERER,
+ types,
+ t->use_ssl ? WINHTTP_FLAG_SECURE : 0);
+
+ if (!s->request) {
+ giterr_set(GITERR_OS, "Failed to open request");
+ goto on_error;
+ }
+
+ /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP
+ * adds itself. This option may not be supported by the underlying
+ * platform, so we do not error-check it */
+ WinHttpSetOption(s->request,
+ WINHTTP_OPTION_PEERDIST_EXTENSION_STATE,
+ &peerdist,
+ sizeof(peerdist));
+
+ /* Send Pragma: no-cache header */
+ if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+
+ /* Send Content-Type header -- only necessary on a POST */
+ if (post_verb == s->verb) {
+ git_buf_clear(&buf);
+ if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0)
+ goto on_error;
+
+ git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf));
+
+ if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) {
+ giterr_set(GITERR_OS, "Failed to add a header to the request");
+ goto on_error;
+ }
+ }
+
+ /* If requested, disable certificate validation */
+ if (t->use_ssl) {
+ int flags;
+
+ if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0)
+ goto on_error;
+
+ if ((GIT_TRANSPORTFLAGS_NO_CHECK_CERT & flags) &&
+ !WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS,
+ (LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) {
+ giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
+ goto on_error;
+ }
+ }
+
+ /* If we have a credential on the subtransport, apply it to the request */
+ if (t->cred &&
+ t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT &&
+ t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC &&
+ apply_basic_credential(s->request, t->cred) < 0)
+ goto on_error;
+
+ /* We've done everything up to calling WinHttpSendRequest. */
+
+ return 0;
+
+on_error:
+ git_buf_free(&buf);
+ return -1;
+}
+
+static int parse_unauthorized_response(
+ HINTERNET request,
+ int *allowed_types,
+ int *auth_mechanism)
+{
+ DWORD index, buf_size, last_error;
+ int error = 0;
+ wchar_t *buf = NULL;
+
+ *allowed_types = 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;
+ }
+ }
+
+ /* 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 (!wcsncmp(buf, basic_authtype, 5) &&
+ (buf[5] == L'\0' || buf[5] == L' ')) {
+ *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
+ *auth_mechanism = GIT_WINHTTP_AUTH_BASIC;
+ }
+ }
+
+ git__free(buf);
+ return error;
+}
+
+static int winhttp_stream_read(
+ git_smart_subtransport_stream *stream,
+ char *buffer,
+ size_t buf_size,
+ size_t *bytes_read)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+ winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+ DWORD dw_bytes_read;
+
+replay:
+ /* Connect if necessary */
+ 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;
+ 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 (!WinHttpReceiveResponse(s->request, 0)) {
+ giterr_set(GITERR_OS, "Failed to receive response");
+ return -1;
+ }
+
+ /* Verify that we got a 200 back */
+ status_code_length = sizeof(status_code);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &status_code, &status_code_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to retreive status code");
+ return -1;
+ }
+
+ /* Handle authentication failures */
+ if (HTTP_STATUS_DENIED == status_code &&
+ get_verb == s->verb && t->owner->cred_acquire_cb) {
+ int allowed_types;
+
+ if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0)
+ return -1;
+
+ if (allowed_types &&
+ (!t->cred || 0 == (t->cred->credtype & allowed_types))) {
+
+ if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, allowed_types) < 0)
+ return -1;
+
+ assert(t->cred);
+
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ s->sent_request = 0;
+
+ /* Successfully acquired a credential */
+ goto replay;
+ }
+ }
+
+ if (HTTP_STATUS_OK != status_code) {
+ giterr_set(GITERR_NET, "Request failed with status code: %d", status_code);
+ return -1;
+ }
+
+ /* Verify that we got the correct content-type back */
+ if (post_verb == s->verb)
+ snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service);
+ else
+ snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service);
+
+ git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8);
+ content_type_length = sizeof(content_type);
+
+ if (!WinHttpQueryHeaders(s->request,
+ WINHTTP_QUERY_CONTENT_TYPE,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ &content_type, &content_type_length,
+ WINHTTP_NO_HEADER_INDEX)) {
+ giterr_set(GITERR_OS, "Failed to retrieve response content-type");
+ return -1;
+ }
+
+ if (wcscmp(expected_content_type, content_type)) {
+ giterr_set(GITERR_NET, "Received unexpected content-type");
+ return -1;
+ }
+
+ s->received_response = 1;
+ }
+
+ if (!WinHttpReadData(s->request,
+ (LPVOID)buffer,
+ buf_size,
+ &dw_bytes_read))
+ {
+ giterr_set(GITERR_OS, "Failed to read data");
+ return -1;
+ }
+
+ *bytes_read = dw_bytes_read;
+
+ return 0;
+}
+
+static int winhttp_stream_write(
+ 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;
+
+ /* 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,
+ WINHTTP_NO_ADDITIONAL_HEADERS, 0,
+ WINHTTP_NO_REQUEST_DATA, 0,
+ (DWORD)len, 0)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ s->sent_request = 1;
+
+ if (!WinHttpWriteData(s->request,
+ (LPCVOID)buffer,
+ (DWORD)len,
+ &bytes_written)) {
+ giterr_set(GITERR_OS, "Failed to send request");
+ return -1;
+ }
+
+ assert((DWORD)len == bytes_written);
+
+ return 0;
+}
+
+static void winhttp_stream_free(git_smart_subtransport_stream *stream)
+{
+ winhttp_stream *s = (winhttp_stream *)stream;
+
+ if (s->request) {
+ WinHttpCloseHandle(s->request);
+ s->request = NULL;
+ }
+
+ git__free(s);
+}
+
+static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_stream **stream)
+{
+ winhttp_stream *s;
+
+ if (!stream)
+ return -1;
+
+ s = (winhttp_stream *)git__calloc(sizeof(winhttp_stream), 1);
+ GITERR_CHECK_ALLOC(s);
+
+ s->parent.subtransport = &t->parent;
+ s->parent.read = winhttp_stream_read;
+ s->parent.write = winhttp_stream_write;
+ s->parent.free = winhttp_stream_free;
+
+ *stream = (git_smart_subtransport_stream *)s;
+ return 0;
+}
+
+static int winhttp_connect(
+ winhttp_subtransport *t,
+ const char *url)
+{
+ wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
+ wchar_t host[GIT_WIN_PATH];
+ int32_t port;
+ const char *default_port;
+ int ret;
+
+ if (!git__prefixcmp(url, prefix_http)) {
+ url = url + strlen(prefix_http);
+ default_port = "80";
+ }
+
+ if (!git__prefixcmp(url, prefix_https)) {
+ url += strlen(prefix_https);
+ default_port = "443";
+ t->use_ssl = 1;
+ }
+
+ if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
+ return ret;
+
+ t->path = strchr(url, '/');
+
+ /* Prepare port */
+ if (git__strtol32(&port, t->port, NULL, 10) < 0)
+ return -1;
+
+ /* Prepare host */
+ git__utf8_to_16(host, GIT_WIN_PATH, t->host);
+
+ /* Establish session */
+ t->session = WinHttpOpen(
+ ua,
+ WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ 0);
+
+ if (!t->session) {
+ giterr_set(GITERR_OS, "Failed to init WinHTTP");
+ return -1;
+ }
+
+ /* Establish connection */
+ t->connection = WinHttpConnect(
+ t->session,
+ host,
+ port,
+ 0);
+
+ if (!t->connection) {
+ giterr_set(GITERR_OS, "Failed to connect to host");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int winhttp_uploadpack_ls(
+ winhttp_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ 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;
+
+ return 0;
+}
+
+static int winhttp_uploadpack(
+ winhttp_subtransport *t,
+ const char *url,
+ git_smart_subtransport_stream **stream)
+{
+ 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_service_url;
+ s->verb = post_verb;
+
+ return 0;
+}
+
+static int winhttp_action(
+ git_smart_subtransport_stream **stream,
+ git_smart_subtransport *smart_transport,
+ const char *url,
+ git_smart_service_t action)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *)smart_transport;
+
+ if (!stream)
+ return -1;
+
+ switch (action)
+ {
+ case GIT_SERVICE_UPLOADPACK_LS:
+ return winhttp_uploadpack_ls(t, url, stream);
+
+ case GIT_SERVICE_UPLOADPACK:
+ return winhttp_uploadpack(t, url, stream);
+ }
+
+ *stream = NULL;
+ return -1;
+}
+
+static void winhttp_free(git_smart_subtransport *smart_transport)
+{
+ winhttp_subtransport *t = (winhttp_subtransport *) smart_transport;
+
+ git__free(t->host);
+ git__free(t->port);
+
+ if (t->cred) {
+ t->cred->free(t->cred);
+ t->cred = NULL;
+ }
+
+ if (t->connection) {
+ WinHttpCloseHandle(t->connection);
+ t->connection = NULL;
+ }
+
+ if (t->session) {
+ WinHttpCloseHandle(t->session);
+ t->session = NULL;
+ }
+
+ git__free(t);
+}
+
+int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner)
+{
+ winhttp_subtransport *t;
+
+ if (!out)
+ return -1;
+
+ t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1);
+ GITERR_CHECK_ALLOC(t);
+
+ t->owner = (transport_smart *)owner;
+ t->parent.action = winhttp_action;
+ t->parent.free = winhttp_free;
+
+ *out = (git_smart_subtransport *) t;
+ return 0;
+}
+
+#endif /* GIT_WINHTTP */
diff --git a/src/tree.c b/src/tree.c
index 8d3f2665c..6f9838880 100644
--- a/src/tree.c
+++ b/src/tree.c
@@ -26,7 +26,9 @@ static bool valid_filemode(const int filemode)
static int valid_entry_name(const char *filename)
{
- return *filename != '\0' && strchr(filename, '/') == NULL;
+ return *filename != '\0' && strchr(filename, '/') == NULL &&
+ strcmp(filename, "..") != 0 && strcmp(filename, ".") != 0 &&
+ strcmp(filename, ".git") != 0;
}
static int entry_sort_cmp(const void *a, const void *b)
@@ -353,7 +355,7 @@ static unsigned int find_next_dir(const char *dirname, git_index *index, unsigne
dirlen = strlen(dirname);
for (i = start; i < entries; ++i) {
- git_index_entry *entry = git_index_get(index, i);
+ git_index_entry *entry = git_index_get_byindex(index, i);
if (strlen(entry->path) < dirlen ||
memcmp(entry->path, dirname, dirlen) ||
(dirlen > 0 && entry->path[dirlen] != '/')) {
@@ -372,6 +374,9 @@ static int append_entry(
{
git_tree_entry *entry;
+ if (!valid_entry_name(filename))
+ return tree_error("Failed to insert entry. Invalid name for a tree entry");
+
entry = alloc_entry(filename);
GITERR_CHECK_ALLOC(entry);
@@ -415,7 +420,7 @@ static int write_tree(
* need to keep track of the current position.
*/
for (i = start; i < entries; ++i) {
- git_index_entry *entry = git_index_get(index, i);
+ git_index_entry *entry = git_index_get_byindex(index, i);
char *filename, *next_slash;
/*
@@ -491,16 +496,17 @@ on_error:
return -1;
}
-int git_tree_create_fromindex(git_oid *oid, git_index *index)
+int git_tree__write_index(git_oid *oid, git_index *index, git_repository *repo)
{
int ret;
- git_repository *repo;
- repo = (git_repository *)GIT_REFCOUNT_OWNER(index);
+ assert(oid && index && repo);
- if (repo == NULL)
- return tree_error("Failed to create tree. "
- "The index file is not backed up by an existing repository");
+ if (git_index_has_conflicts(index)) {
+ giterr_set(GITERR_INDEX,
+ "Cannot create a tree from a not fully merged index.");
+ return GIT_EUNMERGED;
+ }
if (index->tree != NULL && index->tree->entries >= 0) {
git_oid_cpy(oid, &index->tree->oid);
diff --git a/src/tree.h b/src/tree.h
index 24b517ce3..b67c55202 100644
--- a/src/tree.h
+++ b/src/tree.h
@@ -47,6 +47,12 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj);
*/
int git_tree__prefix_position(git_tree *tree, const char *prefix);
+
+/**
+ * Write a tree to the given repository
+ */
+int git_tree__write_index(git_oid *oid, git_index *index, git_repository *repo);
+
/**
* Obsolete mode kept for compatibility reasons
*/
diff --git a/src/unix/posix.h b/src/unix/posix.h
index bcd800301..6980c36f1 100644
--- a/src/unix/posix.h
+++ b/src/unix/posix.h
@@ -21,5 +21,9 @@
#define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__)
#define p_mkstemp(p) mkstemp(p)
#define p_setenv(n,v,o) setenv(n,v,o)
+#define p_inet_pton(a, b, c) inet_pton(a, b, c)
+
+/* see win32/posix.h for explanation about why this exists */
+#define p_lstat_posixly(p,b) lstat(p,b)
#endif
diff --git a/src/util.c b/src/util.c
index 0a82ccea6..3a08d4554 100644
--- a/src/util.c
+++ b/src/util.c
@@ -174,6 +174,36 @@ int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int ba
return error;
}
+int git__strcmp(const char *a, const char *b)
+{
+ while (*a && *b && *a == *b)
+ ++a, ++b;
+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);
+}
+
+int git__strcasecmp(const char *a, const char *b)
+{
+ while (*a && *b && tolower(*a) == tolower(*b))
+ ++a, ++b;
+ return (tolower(*a) - tolower(*b));
+}
+
+int git__strncmp(const char *a, const char *b, size_t sz)
+{
+ while (sz && *a && *b && *a == *b)
+ --sz, ++a, ++b;
+ if (!sz)
+ return 0;
+ return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b);
+}
+
+int git__strncasecmp(const char *a, const char *b, size_t sz)
+{
+ while (sz && *a && *b && tolower(*a) == tolower(*b))
+ --sz, ++a, ++b;
+ return !sz ? 0 : (tolower(*a) - tolower(*b));
+}
+
void git__strntolower(char *str, size_t len)
{
size_t i;
@@ -432,12 +462,8 @@ int git__strcmp_cb(const void *a, const void *b)
int git__parse_bool(int *out, const char *value)
{
/* A missing value means true */
- if (value == NULL) {
- *out = 1;
- return 0;
- }
-
- if (!strcasecmp(value, "true") ||
+ if (value == NULL ||
+ !strcasecmp(value, "true") ||
!strcasecmp(value, "yes") ||
!strcasecmp(value, "on")) {
*out = 1;
@@ -445,7 +471,8 @@ int git__parse_bool(int *out, const char *value)
}
if (!strcasecmp(value, "false") ||
!strcasecmp(value, "no") ||
- !strcasecmp(value, "off")) {
+ !strcasecmp(value, "off") ||
+ value[0] == '\0') {
*out = 0;
return 0;
}
diff --git a/src/util.h b/src/util.h
index 4f83d3bc1..cb1c4fdc2 100644
--- a/src/util.h
+++ b/src/util.h
@@ -42,12 +42,11 @@ GIT_INLINE(char *) git__strdup(const char *str)
GIT_INLINE(char *) git__strndup(const char *str, size_t n)
{
- size_t length;
+ size_t length = 0;
char *ptr;
- length = strlen(str);
- if (n < length)
- length = n;
+ while (length < n && str[length])
+ ++length;
ptr = (char*)malloc(length + 1);
if (!ptr) {
@@ -55,7 +54,9 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n)
return NULL;
}
- memcpy(ptr, str, length);
+ if (length)
+ memcpy(ptr, str, length);
+
ptr[length] = '\0';
return ptr;
@@ -80,6 +81,11 @@ extern int git__prefixcmp(const char *str, const char *prefix);
extern int git__prefixcmp_icase(const char *str, const char *prefix);
extern int git__suffixcmp(const char *str, const char *suffix);
+GIT_INLINE(int) git__signum(int val)
+{
+ return ((val > 0) - (val < 0));
+}
+
extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base);
extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base);
@@ -128,6 +134,11 @@ extern int git__bsearch(
extern int git__strcmp_cb(const void *a, const void *b);
+extern int git__strcmp(const char *a, const char *b);
+extern int git__strcasecmp(const char *a, const char *b);
+extern int git__strncmp(const char *a, const char *b, size_t sz);
+extern int git__strncasecmp(const char *a, const char *b, size_t sz);
+
typedef struct {
short refcount;
void *owner;
diff --git a/src/vector.c b/src/vector.c
index c6a644cc3..4763792f5 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -223,6 +223,20 @@ void git_vector_uniq(git_vector *v)
v->length -= j - i - 1;
}
+void git_vector_remove_matching(git_vector *v, int (*match)(git_vector *v, size_t idx))
+{
+ unsigned int i, j;
+
+ for (i = 0, j = 0; j < v->length; ++j) {
+ v->contents[i] = v->contents[j];
+
+ if (!match(v, i))
+ i++;
+ }
+
+ v->length = i;
+}
+
void git_vector_clear(git_vector *v)
{
assert(v);
@@ -241,3 +255,33 @@ void git_vector_swap(git_vector *a, git_vector *b)
memcpy(a, b, sizeof(t));
memcpy(b, &t, sizeof(t));
}
+
+int git_vector_resize_to(git_vector *v, size_t new_length)
+{
+ if (new_length <= v->length)
+ return 0;
+
+ while (new_length >= v->_alloc_size)
+ if (resize_vector(v) < 0)
+ return -1;
+
+ memset(&v->contents[v->length], 0,
+ sizeof(void *) * (new_length - v->length));
+
+ v->length = new_length;
+
+ return 0;
+}
+
+int git_vector_set(void **old, git_vector *v, size_t position, void *value)
+{
+ if (git_vector_resize_to(v, position + 1) < 0)
+ return -1;
+
+ if (old != NULL)
+ *old = v->contents[position];
+
+ v->contents[position] = value;
+
+ return 0;
+}
diff --git a/src/vector.h b/src/vector.h
index 49ba754f0..6d820b8fc 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -29,17 +29,26 @@ void git_vector_swap(git_vector *a, git_vector *b);
void git_vector_sort(git_vector *v);
+/** Linear search for matching entry using internal comparison function */
int git_vector_search(git_vector *v, const void *entry);
+
+/** Linear search for matching entry using explicit comparison function */
int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key);
+/**
+ * Binary search for matching entry using explicit comparison function that
+ * returns position where item would go if not found.
+ */
int git_vector_bsearch3(
unsigned int *at_pos, git_vector *v, git_vector_cmp cmp, const void *key);
+/** Binary search for matching entry using internal comparison function */
GIT_INLINE(int) git_vector_bsearch(git_vector *v, const void *key)
{
return git_vector_bsearch3(NULL, v, v->_cmp, key);
}
+/** Binary search for matching entry using explicit comparison function */
GIT_INLINE(int) git_vector_bsearch2(
git_vector *v, git_vector_cmp cmp, const void *key)
{
@@ -75,5 +84,9 @@ int git_vector_insert_sorted(git_vector *v, void *element,
int git_vector_remove(git_vector *v, unsigned int idx);
void git_vector_pop(git_vector *v);
void git_vector_uniq(git_vector *v);
+void git_vector_remove_matching(git_vector *v, int (*match)(git_vector *v, size_t idx));
+
+int git_vector_resize_to(git_vector *v, size_t new_length);
+int git_vector_set(void **old, git_vector *v, size_t position, void *value);
#endif
diff --git a/src/win32/posix.h b/src/win32/posix.h
index 80dcca5c1..ee61c2d05 100644
--- a/src/win32/posix.h
+++ b/src/win32/posix.h
@@ -48,5 +48,14 @@ extern int p_getcwd(char *buffer_out, size_t size);
extern int p_rename(const char *from, const char *to);
extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags);
extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags);
+extern int p_inet_pton(int af, const char* src, void* dst);
+
+/* p_lstat is almost but not quite POSIX correct. Specifically, the use of
+ * ENOTDIR is wrong, in that it does not mean precisely that a non-directory
+ * entry was encountered. Making it correct is potentially expensive,
+ * however, so this is a separate version of p_lstat to use when correct
+ * POSIX ENOTDIR semantics is required.
+ */
+extern int p_lstat_posixly(const char *filename, struct stat *buf);
#endif
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 649fe9b95..0efcaf597 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -11,7 +11,7 @@
#include <errno.h>
#include <io.h>
#include <fcntl.h>
-
+#include <ws2tcpip.h>
int p_unlink(const char *path)
{
@@ -52,17 +52,33 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft)
return (time_t)winTime;
}
-static int do_lstat(const char *file_name, struct stat *buf)
+#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\')
+
+static int do_lstat(
+ const char *file_name, struct stat *buf, int posix_enotdir)
{
WIN32_FILE_ATTRIBUTE_DATA fdata;
- wchar_t fbuf[GIT_WIN_PATH];
+ wchar_t fbuf[GIT_WIN_PATH], lastch;
DWORD last_error;
+ int flen;
- git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name);
+ flen = git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name);
+
+ /* truncate trailing slashes */
+ for (; flen > 0; --flen) {
+ lastch = fbuf[flen - 1];
+ if (WIN32_IS_WSEP(lastch))
+ fbuf[flen - 1] = L'\0';
+ else if (lastch != L'\0')
+ break;
+ }
if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
int fMode = S_IREAD;
+ if (!buf)
+ return 0;
+
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
fMode |= S_IFDIR;
else
@@ -84,48 +100,45 @@ static int do_lstat(const char *file_name, struct stat *buf)
buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
+
return 0;
}
- last_error = GetLastError();
- if (last_error == ERROR_FILE_NOT_FOUND)
- errno = ENOENT;
- else if (last_error == ERROR_PATH_NOT_FOUND)
- errno = ENOTDIR;
+ errno = ENOENT;
+
+ /* We need POSIX behavior, then ENOTDIR must set when any of the folders in the
+ * file path is a regular file,otherwise ENOENT must be set.
+ */
+ if (posix_enotdir) {
+ /* scan up path until we find an existing item */
+ while (1) {
+ /* remove last directory component */
+ for (--flen; flen > 0 && !WIN32_IS_WSEP(fbuf[flen]); --flen);
+
+ if (flen <= 0)
+ break;
+
+ fbuf[flen] = L'\0';
+
+ if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) {
+ if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ errno = ENOTDIR;
+ break;
+ }
+ }
+ }
return -1;
}
-int p_lstat(const char *file_name, struct stat *buf)
+int p_lstat(const char *filename, struct stat *buf)
{
- int error;
- size_t namelen;
- char *alt_name;
-
- if (do_lstat(file_name, buf) == 0)
- return 0;
-
- /* if file_name ended in a '/', Windows returned ENOENT;
- * try again without trailing slashes
- */
- namelen = strlen(file_name);
- if (namelen && file_name[namelen-1] != '/')
- return -1;
-
- while (namelen && file_name[namelen-1] == '/')
- --namelen;
-
- if (!namelen)
- return -1;
-
- alt_name = git__strndup(file_name, namelen);
- if (!alt_name)
- return -1;
-
- error = do_lstat(alt_name, buf);
+ return do_lstat(filename, buf, 0);
+}
- git__free(alt_name);
- return error;
+int p_lstat_posixly(const char *filename, struct stat *buf)
+{
+ return do_lstat(filename, buf, 1);
}
int p_readlink(const char *link, char *target, size_t target_len)
@@ -268,7 +281,7 @@ int p_getcwd(char *buffer_out, size_t size)
int p_stat(const char* path, struct stat* buf)
{
- return do_lstat(path, buf);
+ return do_lstat(path, buf, 0);
}
int p_chdir(const char* path)
@@ -301,46 +314,42 @@ int p_hide_directory__w32(const char *path)
char *p_realpath(const char *orig_path, char *buffer)
{
- int ret, buffer_sz = 0;
+ int ret;
wchar_t orig_path_w[GIT_WIN_PATH];
wchar_t buffer_w[GIT_WIN_PATH];
git__utf8_to_16(orig_path_w, GIT_WIN_PATH, orig_path);
+
+ /* Implicitly use GetCurrentDirectory which can be a threading issue */
ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH, buffer_w, NULL);
/* According to MSDN, a return value equals to zero means a failure. */
- if (ret == 0 || ret > GIT_WIN_PATH) {
+ if (ret == 0 || ret > GIT_WIN_PATH)
+ buffer = NULL;
+
+ else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) {
buffer = NULL;
- goto done;
+ errno = ENOENT;
}
- if (buffer == NULL) {
- buffer_sz = WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL);
+ else if (buffer == NULL) {
+ int buffer_sz = WideCharToMultiByte(
+ CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL);
if (!buffer_sz ||
!(buffer = (char *)git__malloc(buffer_sz)) ||
- !WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL))
+ !WideCharToMultiByte(
+ CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL))
{
git__free(buffer);
buffer = NULL;
- goto done;
- }
- } else {
- if (!WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) {
- buffer = NULL;
- goto done;
}
}
- if (!git_path_exists(buffer)) {
- if (buffer_sz > 0)
- git__free(buffer);
-
+ else if (!WideCharToMultiByte(
+ CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL))
buffer = NULL;
- errno = ENOENT;
- }
-done:
if (buffer)
git_path_mkposix(buffer);
@@ -504,3 +513,45 @@ int p_gettimeofday(struct timeval *tv, struct timezone *tz)
return 0;
}
+
+int p_inet_pton(int af, const char* src, void* dst)
+{
+ union {
+ struct sockaddr_in6 sin6;
+ struct sockaddr_in sin;
+ } sa;
+ size_t srcsize;
+
+ switch(af)
+ {
+ case AF_INET:
+ sa.sin.sin_family = AF_INET;
+ srcsize = sizeof (sa.sin);
+ break;
+ case AF_INET6:
+ sa.sin6.sin6_family = AF_INET6;
+ srcsize = sizeof (sa.sin6);
+ break;
+ default:
+ errno = WSAEPFNOSUPPORT;
+ return -1;
+ }
+
+ if (WSAStringToAddress(src, af, NULL, (struct sockaddr *) &sa, &srcsize) != 0)
+ {
+ errno = WSAGetLastError();
+ return -1;
+ }
+
+ switch(af)
+ {
+ case AF_INET:
+ memcpy(dst, &sa.sin.sin_addr, sizeof(sa.sin.sin_addr));
+ break;
+ case AF_INET6:
+ memcpy(dst, &sa.sin6.sin6_addr, sizeof(sa.sin6.sin6_addr));
+ break;
+ }
+
+ return 1;
+}
diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c
index 396af7cad..0659a5d1c 100644
--- a/src/win32/utf-conv.c
+++ b/src/win32/utf-conv.c
@@ -70,12 +70,12 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
}
#endif
-void git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
+int git__utf8_to_16(wchar_t *dest, size_t length, const char *src)
{
- MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length);
+ return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length);
}
-void git__utf16_to_8(char *out, const wchar_t *input)
+int git__utf16_to_8(char *out, const wchar_t *input)
{
- WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL);
+ return WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL);
}
diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h
index 3bd1549bc..f62863a76 100644
--- a/src/win32/utf-conv.h
+++ b/src/win32/utf-conv.h
@@ -12,8 +12,8 @@
#define GIT_WIN_PATH (260 + 1)
-void git__utf8_to_16(wchar_t *dest, size_t length, const char *src);
-void git__utf16_to_8(char *dest, const wchar_t *src);
+int git__utf8_to_16(wchar_t *dest, size_t length, const char *src);
+int git__utf16_to_8(char *dest, const wchar_t *src);
#endif
diff --git a/tests-clar/attr/repo.c b/tests-clar/attr/repo.c
index 4a317e4f3..b51d5e335 100644
--- a/tests-clar/attr/repo.c
+++ b/tests-clar/attr/repo.c
@@ -270,12 +270,12 @@ static void assert_proper_normalization(git_index *index, const char *filename,
git_index_entry *entry;
add_to_workdir(filename, CONTENT);
- cl_git_pass(git_index_add(index, filename, 0));
+ cl_git_pass(git_index_add_from_workdir(index, filename));
index_pos = git_index_find(index, filename);
cl_assert(index_pos >= 0);
- entry = git_index_get(index, index_pos);
+ entry = git_index_get_byindex(index, index_pos);
cl_assert_equal_i(0, git_oid_streq(&entry->oid, expected_sha));
}
diff --git a/tests-clar/buf/splice.c b/tests-clar/buf/splice.c
new file mode 100644
index 000000000..e80c93105
--- /dev/null
+++ b/tests-clar/buf/splice.c
@@ -0,0 +1,93 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+
+static git_buf _buf;
+
+void test_buf_splice__initialize(void) {
+ git_buf_init(&_buf, 16);
+}
+
+void test_buf_splice__cleanup(void) {
+ git_buf_free(&_buf);
+}
+
+void test_buf_splice__preprend(void)
+{
+ git_buf_sets(&_buf, "world!");
+
+ cl_git_pass(git_buf_splice(&_buf, 0, 0, "Hello Dolly", strlen("Hello ")));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
+
+void test_buf_splice__append(void)
+{
+ git_buf_sets(&_buf, "Hello");
+
+ cl_git_pass(git_buf_splice(&_buf, git_buf_len(&_buf), 0, " world!", strlen(" world!")));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
+
+void test_buf_splice__insert_at(void)
+{
+ git_buf_sets(&_buf, "Hell world!");
+
+ cl_git_pass(git_buf_splice(&_buf, strlen("Hell"), 0, "o", strlen("o")));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
+
+void test_buf_splice__remove_at(void)
+{
+ git_buf_sets(&_buf, "Hello world of warcraft!");
+
+ cl_git_pass(git_buf_splice(&_buf, strlen("Hello world"), strlen(" of warcraft"), "", 0));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
+
+void test_buf_splice__replace(void)
+{
+ git_buf_sets(&_buf, "Hell0 w0rld!");
+
+ cl_git_pass(git_buf_splice(&_buf, strlen("Hell"), strlen("0 w0"), "o wo", strlen("o wo")));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
+
+void test_buf_splice__replace_with_longer(void)
+{
+ git_buf_sets(&_buf, "Hello you!");
+
+ cl_git_pass(git_buf_splice(&_buf, strlen("Hello "), strlen("you"), "world", strlen("world")));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
+
+void test_buf_splice__replace_with_shorter(void)
+{
+ git_buf_sets(&_buf, "Brave new world!");
+
+ cl_git_pass(git_buf_splice(&_buf, 0, strlen("Brave new"), "Hello", strlen("Hello")));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
+
+void test_buf_splice__truncate(void)
+{
+ git_buf_sets(&_buf, "Hello world!!");
+
+ cl_git_pass(git_buf_splice(&_buf, strlen("Hello world!"), strlen("!"), "", 0));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
+
+void test_buf_splice__dont_do_anything(void)
+{
+ git_buf_sets(&_buf, "Hello world!");
+
+ cl_git_pass(git_buf_splice(&_buf, 3, 0, "Hello", 0));
+
+ cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf));
+}
diff --git a/tests-clar/checkout/head.c b/tests-clar/checkout/head.c
index d36034c52..103b9999e 100644
--- a/tests-clar/checkout/head.c
+++ b/tests-clar/checkout/head.c
@@ -18,5 +18,5 @@ void test_checkout_head__checking_out_an_orphaned_head_returns_GIT_EORPHANEDHEAD
{
make_head_orphaned(g_repo, NON_EXISTING_HEAD);
- cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL, NULL));
+ cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL));
}
diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c
index f017a0fe2..c7b19dba6 100644
--- a/tests-clar/checkout/index.c
+++ b/tests-clar/checkout/index.c
@@ -14,7 +14,7 @@ static void reset_index_to_treeish(git_object *treeish)
cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJ_TREE));
cl_git_pass(git_repository_index(&index, g_repo));
- cl_git_pass(git_index_read_tree(index, (git_tree *)tree, NULL));
+ cl_git_pass(git_index_read_tree(index, (git_tree *)tree));
cl_git_pass(git_index_write(index));
git_object_free(tree);
@@ -26,7 +26,7 @@ void test_checkout_index__initialize(void)
git_tree *tree;
memset(&g_opts, 0, sizeof(g_opts));
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
g_repo = cl_git_sandbox_init("testrepo");
@@ -78,8 +78,7 @@ void test_checkout_index__can_create_missing_files(void)
cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/README", "hey there\n");
test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
@@ -94,8 +93,8 @@ void test_checkout_index__can_remove_untracked_files(void)
cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir"));
- g_opts.checkout_strategy = GIT_CHECKOUT_REMOVE_UNTRACKED;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ g_opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED;
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
cl_assert_equal_i(false, git_path_isdir("./testrepo/dir"));
}
@@ -111,7 +110,7 @@ void test_checkout_index__honor_the_specified_pathspecs(void)
cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt"));
cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt"));
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
cl_assert_equal_i(false, git_path_isfile("./testrepo/README"));
test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n");
@@ -142,7 +141,7 @@ void test_checkout_index__honor_the_gitattributes_directives(void)
cl_git_mkfile("./testrepo/.gitattributes", attributes);
set_core_autocrlf_to(false);
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/README", "hey there\n");
test_file_contents("./testrepo/new.txt", "my new file\n");
@@ -157,7 +156,7 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void)
cl_git_pass(p_unlink("./testrepo/.gitattributes"));
set_core_autocrlf_to(true);
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/README", expected_readme_text);
#endif
@@ -172,7 +171,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void)
{
set_repo_symlink_handling_cap_to(true);
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
#ifdef GIT_WIN32
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
@@ -194,7 +193,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void)
{
set_repo_symlink_handling_cap_to(false);
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/link_to_new.txt", "new.txt");
}
@@ -203,8 +202,12 @@ void test_checkout_index__donot_overwrite_modified_file_by_default(void)
{
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
- g_opts.checkout_strategy = 0;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ /* set this up to not return an error code on conflicts, but it
+ * still will not have permission to overwrite anything...
+ */
+ g_opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/new.txt", "This isn't what's stored!");
}
@@ -213,8 +216,9 @@ void test_checkout_index__can_overwrite_modified_file(void)
{
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
- g_opts.checkout_strategy = GIT_CHECKOUT_OVERWRITE_MODIFIED;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/new.txt", "my new file\n");
}
@@ -224,14 +228,14 @@ void test_checkout_index__options_disable_filters(void)
cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n");
g_opts.disable_filters = false;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/new.txt", "my new file\r\n");
p_unlink("./testrepo/new.txt");
g_opts.disable_filters = true;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/new.txt", "my new file\n");
}
@@ -249,7 +253,7 @@ void test_checkout_index__options_dir_modes(void)
reset_index_to_treeish((git_object *)commit);
g_opts.dir_mode = 0701;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
cl_git_pass(p_stat("./testrepo/a", &st));
cl_assert_equal_i(st.st_mode & 0777, 0701);
@@ -269,7 +273,7 @@ void test_checkout_index__options_override_file_modes(void)
g_opts.file_mode = 0700;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
cl_git_pass(p_stat("./testrepo/new.txt", &st));
cl_assert_equal_i(st.st_mode & 0777, 0700);
@@ -282,28 +286,30 @@ void test_checkout_index__options_open_flags(void)
g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND;
- g_opts.checkout_strategy |= GIT_CHECKOUT_OVERWRITE_MODIFIED;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED;
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
test_file_contents("./testrepo/new.txt", "hi\nmy new file\n");
}
-struct notify_data {
+struct conflict_data {
const char *file;
const char *sha;
};
-static int notify_cb(
- const char *skipped_file,
+static int conflict_cb(
+ const char *conflict_file,
const git_oid *blob_oid,
- int file_mode,
+ unsigned int index_mode,
+ unsigned int wd_mode,
void *payload)
{
- struct notify_data *expectations = (struct notify_data *)payload;
+ struct conflict_data *expectations = (struct conflict_data *)payload;
- GIT_UNUSED(file_mode);
+ GIT_UNUSED(index_mode);
+ GIT_UNUSED(wd_mode);
- cl_assert_equal_s(expectations->file, skipped_file);
+ cl_assert_equal_s(expectations->file, conflict_file);
cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha));
return 0;
@@ -311,7 +317,7 @@ static int notify_cb(
void test_checkout_index__can_notify_of_skipped_files(void)
{
- struct notify_data data;
+ struct conflict_data data;
cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!");
@@ -324,22 +330,24 @@ void test_checkout_index__can_notify_of_skipped_files(void)
data.file = "new.txt";
data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd";
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
- g_opts.skipped_notify_cb = notify_cb;
- g_opts.notify_payload = &data;
+ g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
+ g_opts.conflict_cb = conflict_cb;
+ g_opts.conflict_payload = &data;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
}
-static int dont_notify_cb(
- const char *skipped_file,
+static int dont_conflict_cb(
+ const char *conflict_file,
const git_oid *blob_oid,
- int file_mode,
+ unsigned int index_mode,
+ unsigned int wd_mode,
void *payload)
{
- GIT_UNUSED(skipped_file);
+ GIT_UNUSED(conflict_file);
GIT_UNUSED(blob_oid);
- GIT_UNUSED(file_mode);
+ GIT_UNUSED(index_mode);
+ GIT_UNUSED(wd_mode);
GIT_UNUSED(payload);
cl_assert(false);
@@ -351,12 +359,70 @@ void test_checkout_index__wont_notify_of_expected_line_ending_changes(void)
{
cl_git_pass(p_unlink("./testrepo/.gitattributes"));
set_core_autocrlf_to(true);
-
+
cl_git_mkfile("./testrepo/new.txt", "my new file\r\n");
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
- g_opts.skipped_notify_cb = dont_notify_cb;
- g_opts.notify_payload = NULL;
+ g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
+ g_opts.conflict_cb = dont_conflict_cb;
+ g_opts.conflict_payload = NULL;
- cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL));
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
+}
+
+static void progress(const char *path, size_t cur, size_t tot, void *payload)
+{
+ bool *was_called = (bool*)payload;
+ GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
+ *was_called = true;
+}
+
+void test_checkout_index__calls_progress_callback(void)
+{
+ bool was_called = 0;
+ g_opts.progress_cb = progress;
+ g_opts.progress_payload = &was_called;
+
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
+ cl_assert_equal_i(was_called, true);
+}
+
+void test_checkout_index__can_overcome_name_clashes(void)
+{
+ git_index *index;
+
+ cl_git_pass(git_repository_index(&index, g_repo));
+ git_index_clear(index);
+
+ cl_git_mkfile("./testrepo/path0", "content\r\n");
+ cl_git_pass(p_mkdir("./testrepo/path1", 0777));
+ cl_git_mkfile("./testrepo/path1/file1", "content\r\n");
+
+ cl_git_pass(git_index_add_from_workdir(index, "path0"));
+ cl_git_pass(git_index_add_from_workdir(index, "path1/file1"));
+
+
+ cl_git_pass(p_unlink("./testrepo/path0"));
+ cl_git_pass(git_futils_rmdir_r(
+ "./testrepo/path1", NULL, GIT_RMDIR_REMOVE_FILES));
+
+ cl_git_mkfile("./testrepo/path1", "content\r\n");
+ cl_git_pass(p_mkdir("./testrepo/path0", 0777));
+ cl_git_mkfile("./testrepo/path0/file0", "content\r\n");
+
+ cl_assert(git_path_isfile("./testrepo/path1"));
+ cl_assert(git_path_isfile("./testrepo/path0/file0"));
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
+
+ cl_assert(git_path_isfile("./testrepo/path1"));
+ cl_assert(git_path_isfile("./testrepo/path0/file0"));
+
+ g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+ cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts));
+
+ cl_assert(git_path_isfile("./testrepo/path0"));
+ cl_assert(git_path_isfile("./testrepo/path1/file1"));
+
+ git_index_free(index);
}
diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c
index 6d573bfd7..983425324 100644
--- a/tests-clar/checkout/tree.c
+++ b/tests-clar/checkout/tree.c
@@ -12,7 +12,7 @@ void test_checkout_tree__initialize(void)
g_repo = cl_git_sandbox_init("testrepo");
memset(&g_opts, 0, sizeof(g_opts));
- g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
}
void test_checkout_tree__cleanup(void)
@@ -27,7 +27,7 @@ void test_checkout_tree__cannot_checkout_a_non_treeish(void)
/* blob */
cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"));
- cl_git_fail(git_checkout_tree(g_repo, g_object, NULL, NULL));
+ cl_git_fail(git_checkout_tree(g_repo, g_object, NULL));
}
void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void)
@@ -41,7 +41,7 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void)
cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
- cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL));
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt"));
cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt"));
@@ -58,8 +58,29 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void)
cl_assert_equal_i(false, git_path_isdir("./testrepo/de/"));
- cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL));
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
cl_assert_equal_i(true, git_path_isfile("./testrepo/de/2.txt"));
cl_assert_equal_i(true, git_path_isfile("./testrepo/de/fgh/1.txt"));
}
+
+static void progress(const char *path, size_t cur, size_t tot, void *payload)
+{
+ bool *was_called = (bool*)payload;
+ GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
+ *was_called = true;
+}
+
+void test_checkout_tree__calls_progress_callback(void)
+{
+ bool was_called = 0;
+
+ g_opts.progress_cb = progress;
+ g_opts.progress_payload = &was_called;
+
+ cl_git_pass(git_revparse_single(&g_object, g_repo, "master"));
+
+ cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
+
+ cl_assert_equal_i(was_called, true);
+}
diff --git a/tests-clar/checkout/typechange.c b/tests-clar/checkout/typechange.c
index f013617d5..cd34885de 100644
--- a/tests-clar/checkout/typechange.c
+++ b/tests-clar/checkout/typechange.c
@@ -40,16 +40,21 @@ void test_checkout_typechange__checkout_typechanges(void)
git_object *obj;
git_checkout_opts opts = {0};
- opts.checkout_strategy =
- GIT_CHECKOUT_REMOVE_UNTRACKED |
- GIT_CHECKOUT_CREATE_MISSING |
- GIT_CHECKOUT_OVERWRITE_MODIFIED;
+ opts.checkout_strategy = GIT_CHECKOUT_FORCE;
+
+ /* if you don't include GIT_CHECKOUT_REMOVE_UNTRACKED then on the final
+ * checkout which is supposed to remove all the files, we will not
+ * actually remove them!
+ */
for (i = 0; g_typechange_oids[i] != NULL; ++i) {
cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
/* fprintf(stderr, "checking out '%s'\n", g_typechange_oids[i]); */
- cl_git_pass(git_checkout_tree(g_repo, obj, &opts, NULL));
+ cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
+
+ cl_git_pass(
+ git_repository_set_head_detached(g_repo, git_object_id(obj)));
git_object_free(obj);
diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_helpers.c
index 647ea5201..b718d4305 100644
--- a/tests-clar/clar_helpers.c
+++ b/tests-clar/clar_helpers.c
@@ -1,5 +1,6 @@
#include "clar_libgit2.h"
#include "posix.h"
+#include "path.h"
void clar_on_init(void)
{
@@ -89,7 +90,11 @@ int cl_setenv(const char *name, const char *value)
if (value != NULL)
git__utf8_to_16(value_utf16, GIT_WIN_PATH, value);
- cl_assert(SetEnvironmentVariableW(name_utf16, value ? value_utf16 : NULL));
+ /* Windows XP returns 0 (failed) when passing NULL for lpValue when lpName
+ * does not exist in the environment block. This behavior seems to have changed
+ * in later versions. Don't fail when SetEnvironmentVariable fails, if we passed
+ * NULL for lpValue. */
+ cl_assert(SetEnvironmentVariableW(name_utf16, value ? value_utf16 : NULL) || !value);
return 0;
}
@@ -218,3 +223,100 @@ bool cl_is_chmod_supported(void)
return _is_supported;
}
+const char* cl_git_fixture_url(const char *fixturename)
+{
+ return cl_git_path_url(cl_fixture(fixturename));
+}
+
+const char* cl_git_path_url(const char *path)
+{
+ static char url[4096];
+
+ const char *in_buf;
+ git_buf path_buf = GIT_BUF_INIT;
+ git_buf url_buf = GIT_BUF_INIT;
+
+ cl_git_pass(git_path_prettify_dir(&path_buf, path, NULL));
+ cl_git_pass(git_buf_puts(&url_buf, "file://"));
+
+#ifdef _MSC_VER
+ /*
+ * A FILE uri matches the following format: file://[host]/path
+ * where "host" can be empty and "path" is an absolute path to the resource.
+ *
+ * In this test, no hostname is used, but we have to ensure the leading triple slashes:
+ *
+ * *nix: file:///usr/home/...
+ * Windows: file:///C:/Users/...
+ */
+ cl_git_pass(git_buf_putc(&url_buf, '/'));
+#endif
+
+ in_buf = git_buf_cstr(&path_buf);
+
+ /*
+ * A very hacky Url encoding that only takes care of escaping the spaces
+ */
+ while (*in_buf) {
+ if (*in_buf == ' ')
+ cl_git_pass(git_buf_puts(&url_buf, "%20"));
+ else
+ cl_git_pass(git_buf_putc(&url_buf, *in_buf));
+
+ in_buf++;
+ }
+
+ strncpy(url, git_buf_cstr(&url_buf), 4096);
+ git_buf_free(&url_buf);
+ git_buf_free(&path_buf);
+ return url;
+}
+
+typedef struct {
+ const char *filename;
+ size_t filename_len;
+} remove_data;
+
+static int remove_placeholders_recurs(void *_data, git_buf *path)
+{
+ remove_data *data = (remove_data *)_data;
+ size_t pathlen;
+
+ if (git_path_isdir(path->ptr) == true)
+ return git_path_direach(path, remove_placeholders_recurs, data);
+
+ pathlen = path->size;
+
+ if (pathlen < data->filename_len)
+ return 0;
+
+ /* if path ends in '/'+filename (or equals filename) */
+ if (!strcmp(data->filename, path->ptr + pathlen - data->filename_len) &&
+ (pathlen == data->filename_len ||
+ path->ptr[pathlen - data->filename_len - 1] == '/'))
+ return p_unlink(path->ptr);
+
+ return 0;
+}
+
+int cl_git_remove_placeholders(const char *directory_path, const char *filename)
+{
+ int error;
+ remove_data data;
+ git_buf buffer = GIT_BUF_INIT;
+
+ if (git_path_isdir(directory_path) == false)
+ return -1;
+
+ if (git_buf_sets(&buffer, directory_path) < 0)
+ return -1;
+
+ data.filename = filename;
+ data.filename_len = strlen(filename);
+
+ error = remove_placeholders_recurs(&data, &buffer);
+
+ git_buf_free(&buffer);
+
+ return error;
+}
diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h
index ce3688ec4..91a542654 100644
--- a/tests-clar/clar_libgit2.h
+++ b/tests-clar/clar_libgit2.h
@@ -57,4 +57,11 @@ int cl_rename(const char *source, const char *dest);
git_repository *cl_git_sandbox_init(const char *sandbox);
void cl_git_sandbox_cleanup(void);
+/* Local-repo url helpers */
+const char* cl_git_fixture_url(const char *fixturename);
+const char* cl_git_path_url(const char *path);
+
+/* Test repository cleaner */
+int cl_git_remove_placeholders(const char *directory_path, const char *filename);
+
#endif
diff --git a/tests-clar/clone/network.c b/tests-clar/clone/network.c
index 1ebdfb5d1..1304f7728 100644
--- a/tests-clar/clone/network.c
+++ b/tests-clar/clone/network.c
@@ -43,7 +43,7 @@ void test_clone_network__network_bare(void)
cl_set_cleanup(&cleanup_repository, "./test");
- cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "./test", NULL));
+ cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "./test", NULL, NULL));
cl_assert(git_repository_is_bare(g_repo));
cl_git_pass(git_remote_load(&origin, g_repo, "origin"));
@@ -91,18 +91,36 @@ void test_clone_network__can_prevent_the_checkout_of_a_standard_repo(void)
git_buf_free(&path);
}
+static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload)
+{
+ bool *was_called = (bool*)payload;
+ GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
+ (*was_called) = true;
+}
+
+static void fetch_progress(const git_transfer_progress *stats, void *payload)
+{
+ bool *was_called = (bool*)payload;
+ GIT_UNUSED(stats);
+ (*was_called) = true;
+}
+
void test_clone_network__can_checkout_a_cloned_repo(void)
{
- git_checkout_opts opts;
+ git_checkout_opts opts = {0};
git_buf path = GIT_BUF_INIT;
git_reference *head;
+ bool checkout_progress_cb_was_called = false,
+ fetch_progress_cb_was_called = false;
- memset(&opts, 0, sizeof(opts));
- opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING;
+ opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+ opts.progress_cb = &checkout_progress;
+ opts.progress_payload = &checkout_progress_cb_was_called;
cl_set_cleanup(&cleanup_repository, "./default-checkout");
- cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./default-checkout", NULL, NULL, &opts));
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./default-checkout",
+ &fetch_progress, &fetch_progress_cb_was_called, &opts));
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt"));
cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&path)));
@@ -111,6 +129,9 @@ void test_clone_network__can_checkout_a_cloned_repo(void)
cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
cl_assert_equal_s("refs/heads/master", git_reference_target(head));
+ cl_assert_equal_i(true, checkout_progress_cb_was_called);
+ cl_assert_equal_i(true, fetch_progress_cb_was_called);
+
git_reference_free(head);
git_buf_free(&path);
}
diff --git a/tests-clar/clone/nonetwork.c b/tests-clar/clone/nonetwork.c
index 81f95b9b3..59f43362f 100644
--- a/tests-clar/clone/nonetwork.c
+++ b/tests-clar/clone/nonetwork.c
@@ -3,7 +3,6 @@
#include "git2/clone.h"
#include "repository.h"
-#define DO_LOCAL_TEST 0
#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository"
static git_repository *g_repo;
@@ -20,81 +19,29 @@ static void cleanup_repository(void *path)
cl_fixture_cleanup((const char *)path);
}
-// TODO: This is copy/pasted from network/remotelocal.c.
-static void build_local_file_url(git_buf *out, const char *fixture)
-{
- const char *in_buf;
-
- git_buf path_buf = GIT_BUF_INIT;
-
- cl_git_pass(git_path_prettify_dir(&path_buf, fixture, NULL));
- cl_git_pass(git_buf_puts(out, "file://"));
-
-#ifdef GIT_WIN32
- /*
- * A FILE uri matches the following format: file://[host]/path
- * where "host" can be empty and "path" is an absolute path to the resource.
- *
- * In this test, no hostname is used, but we have to ensure the leading triple slashes:
- *
- * *nix: file:///usr/home/...
- * Windows: file:///C:/Users/...
- */
- cl_git_pass(git_buf_putc(out, '/'));
-#endif
-
- in_buf = git_buf_cstr(&path_buf);
-
- /*
- * A very hacky Url encoding that only takes care of escaping the spaces
- */
- while (*in_buf) {
- if (*in_buf == ' ')
- cl_git_pass(git_buf_puts(out, "%20"));
- else
- cl_git_pass(git_buf_putc(out, *in_buf));
-
- in_buf++;
- }
-
- git_buf_free(&path_buf);
-}
-
void test_clone_nonetwork__bad_url(void)
{
/* Clone should clean up the mess if the URL isn't a git repository */
cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", NULL, NULL, NULL));
cl_assert(!git_path_exists("./foo"));
- cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git", NULL));
+ cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git", NULL, NULL));
cl_assert(!git_path_exists("./foo.git"));
}
void test_clone_nonetwork__local(void)
{
- git_buf src = GIT_BUF_INIT;
- build_local_file_url(&src, cl_fixture("testrepo.git"));
-
-#if DO_LOCAL_TEST
+ const char *src = cl_git_fixture_url("testrepo.git");
cl_set_cleanup(&cleanup_repository, "./local");
- cl_git_pass(git_clone(&g_repo, git_buf_cstr(&src), "./local", NULL, NULL, NULL));
-#endif
-
- git_buf_free(&src);
+ cl_git_pass(git_clone(&g_repo, src, "./local", NULL, NULL, NULL));
}
void test_clone_nonetwork__local_bare(void)
{
- git_buf src = GIT_BUF_INIT;
- build_local_file_url(&src, cl_fixture("testrepo.git"));
-
-#if DO_LOCAL_TEST
+ const char *src = cl_git_fixture_url("testrepo.git");
cl_set_cleanup(&cleanup_repository, "./local.git");
- cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL));
-#endif
-
- git_buf_free(&src);
+ cl_git_pass(git_clone_bare(&g_repo, src, "./local.git", NULL, NULL));
}
void test_clone_nonetwork__fail_when_the_target_is_a_file(void)
diff --git a/tests-clar/config/config_helpers.c b/tests-clar/config/config_helpers.c
new file mode 100644
index 000000000..53bd945a0
--- /dev/null
+++ b/tests-clar/config/config_helpers.c
@@ -0,0 +1,37 @@
+#include "clar_libgit2.h"
+#include "config_helpers.h"
+#include "repository.h"
+
+void assert_config_entry_existence(
+ git_repository *repo,
+ const char *name,
+ bool is_supposed_to_exist)
+{
+ git_config *config;
+ const char *out;
+ int result;
+
+ cl_git_pass(git_repository_config__weakptr(&config, repo));
+
+ result = git_config_get_string(&out, config, name);
+
+ if (is_supposed_to_exist)
+ cl_git_pass(result);
+ else
+ cl_assert_equal_i(GIT_ENOTFOUND, result);
+}
+
+void assert_config_entry_value(
+ git_repository *repo,
+ const char *name,
+ const char *expected_value)
+{
+ git_config *config;
+ const char *out;
+
+ cl_git_pass(git_repository_config__weakptr(&config, repo));
+
+ cl_git_pass(git_config_get_string(&out, config, name));
+
+ cl_assert_equal_s(expected_value, out);
+}
diff --git a/tests-clar/config/config_helpers.h b/tests-clar/config/config_helpers.h
new file mode 100644
index 000000000..b887b3d38
--- /dev/null
+++ b/tests-clar/config/config_helpers.h
@@ -0,0 +1,9 @@
+extern void assert_config_entry_existence(
+ git_repository *repo,
+ const char *name,
+ bool is_supposed_to_exist);
+
+extern void assert_config_entry_value(
+ git_repository *repo,
+ const char *name,
+ const char *expected_value);
diff --git a/tests-clar/config/configlevel.c b/tests-clar/config/configlevel.c
index d947856fa..1c22e8d9f 100644
--- a/tests-clar/config/configlevel.c
+++ b/tests-clar/config/configlevel.c
@@ -57,3 +57,15 @@ void test_config_configlevel__can_read_from_a_single_level_focused_file_after_pa
git_config_free(single_level_cfg);
}
+
+void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_returns_ENOTFOUND(void)
+{
+ git_config *cfg;
+ git_config *local_cfg;
+
+ cl_git_pass(git_config_new(&cfg));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_config_open_level(&local_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL));
+
+ git_config_free(cfg);
+}
diff --git a/tests-clar/config/new.c b/tests-clar/config/new.c
index 6bd719fba..dd6dbca9e 100644
--- a/tests-clar/config/new.c
+++ b/tests-clar/config/new.c
@@ -11,6 +11,7 @@ void test_config_new__write_new_config(void)
const char *out;
git_config *config;
+ cl_git_mkfile(TEST_CONFIG, "");
cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG));
cl_git_pass(git_config_set_string(config, "color.ui", "auto"));
diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c
index cf781e6bf..e0171a593 100644
--- a/tests-clar/config/read.c
+++ b/tests-clar/config/read.c
@@ -87,12 +87,20 @@ void test_config_read__lone_variable(void)
cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config4")));
+ cl_git_fail(git_config_get_int32(&i, cfg, "some.section.variable"));
+
cl_git_pass(git_config_get_string(&str, cfg, "some.section.variable"));
- cl_assert(str == NULL);
+ cl_assert_equal_s(str, "");
cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variable"));
cl_assert(i == 1);
+ cl_git_pass(git_config_get_string(&str, cfg, "some.section.variableeq"));
+ cl_assert_equal_s(str, "");
+
+ cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variableeq"));
+ cl_assert(i == 0);
+
git_config_free(cfg);
}
@@ -305,7 +313,7 @@ void test_config_read__read_git_config_entry(void)
cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"),
GIT_CONFIG_LEVEL_SYSTEM, 0));
- cl_git_pass(git_config_get_config_entry(&entry, cfg, "core.dummy2"));
+ cl_git_pass(git_config_get_entry(&entry, cfg, "core.dummy2"));
cl_assert_equal_s("core.dummy2", entry->name);
cl_assert_equal_s("42", entry->value);
cl_assert_equal_i(GIT_CONFIG_LEVEL_SYSTEM, entry->level);
@@ -422,3 +430,22 @@ void test_config_read__simple_read_from_specific_level(void)
git_config_free(cfg_specific);
git_config_free(cfg);
}
+
+void test_config_read__can_load_and_parse_an_empty_config_file(void)
+{
+ git_config *cfg;
+ int i;
+
+ cl_git_mkfile("./empty", "");
+ cl_git_pass(git_config_open_ondisk(&cfg, "./empty"));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_config_get_int32(&i, cfg, "nope.neither"));
+
+ git_config_free(cfg);
+}
+
+void test_config_read__cannot_load_a_non_existing_config_file(void)
+{
+ git_config *cfg;
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_config_open_ondisk(&cfg, "./no.config"));
+}
diff --git a/tests-clar/config/refresh.c b/tests-clar/config/refresh.c
new file mode 100644
index 000000000..99d677f0e
--- /dev/null
+++ b/tests-clar/config/refresh.c
@@ -0,0 +1,67 @@
+#include "clar_libgit2.h"
+
+#define TEST_FILE "config.refresh"
+
+void test_config_refresh__initialize(void)
+{
+}
+
+void test_config_refresh__cleanup(void)
+{
+ cl_fixture_cleanup(TEST_FILE);
+}
+
+void test_config_refresh__update_value(void)
+{
+ git_config *cfg;
+ int32_t v;
+
+ cl_git_mkfile(TEST_FILE, "[section]\n\tvalue = 1\n\n");
+
+ /* By freeing the config, we make sure we flush the values */
+ cl_git_pass(git_config_open_ondisk(&cfg, TEST_FILE));
+
+ cl_git_pass(git_config_get_int32(&v, cfg, "section.value"));
+ cl_assert_equal_i(1, v);
+
+ cl_git_rewritefile(TEST_FILE, "[section]\n\tvalue = 10\n\n");
+
+ cl_git_pass(git_config_get_int32(&v, cfg, "section.value"));
+ cl_assert_equal_i(1, v);
+
+ cl_git_pass(git_config_refresh(cfg));
+
+ cl_git_pass(git_config_get_int32(&v, cfg, "section.value"));
+ cl_assert_equal_i(10, v);
+
+ git_config_free(cfg);
+}
+
+void test_config_refresh__delete_value(void)
+{
+ git_config *cfg;
+ int32_t v;
+
+ cl_git_mkfile(TEST_FILE, "[section]\n\tvalue = 1\n\n");
+
+ /* By freeing the config, we make sure we flush the values */
+ cl_git_pass(git_config_open_ondisk(&cfg, TEST_FILE));
+
+ cl_git_pass(git_config_get_int32(&v, cfg, "section.value"));
+ cl_assert_equal_i(1, v);
+ cl_git_fail(git_config_get_int32(&v, cfg, "section.newval"));
+
+ cl_git_rewritefile(TEST_FILE, "[section]\n\tnewval = 10\n\n");
+
+ cl_git_pass(git_config_get_int32(&v, cfg, "section.value"));
+ cl_assert_equal_i(1, v);
+ cl_git_fail(git_config_get_int32(&v, cfg, "section.newval"));
+
+ cl_git_pass(git_config_refresh(cfg));
+
+ cl_git_fail(git_config_get_int32(&v, cfg, "section.value"));
+ cl_git_pass(git_config_get_int32(&v, cfg, "section.newval"));
+ cl_assert_equal_i(10, v);
+
+ git_config_free(cfg);
+}
diff --git a/tests-clar/core/copy.c b/tests-clar/core/copy.c
index d0b21f6ec..c0c59c056 100644
--- a/tests-clar/core/copy.c
+++ b/tests-clar/core/copy.c
@@ -41,7 +41,7 @@ void test_core_copy__file_in_dir(void)
cl_assert(S_ISREG(st.st_mode));
cl_assert(strlen(content) == (size_t)st.st_size);
- cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_RMDIR_REMOVE_FILES));
cl_assert(!git_path_isdir("an_dir"));
}
@@ -95,7 +95,7 @@ void test_core_copy__tree(void)
cl_assert(S_ISLNK(st.st_mode));
#endif
- cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_RMDIR_REMOVE_FILES));
cl_assert(!git_path_isdir("t1"));
/* copy with empty dirs, no links, yes dotfiles, no overwrite */
@@ -119,8 +119,8 @@ void test_core_copy__tree(void)
cl_git_fail(git_path_lstat("t2/c/d/l1", &st));
#endif
- cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES));
cl_assert(!git_path_isdir("t2"));
- cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES));
}
diff --git a/tests-clar/core/env.c b/tests-clar/core/env.c
index 288222d29..d849f76ed 100644
--- a/tests-clar/core/env.c
+++ b/tests-clar/core/env.c
@@ -84,13 +84,15 @@ void test_core_env__0(void)
cl_git_mkfile(path.ptr, "find me");
git_buf_rtruncate_at_char(&path, '/');
- cl_git_fail(git_futils_find_global_file(&found, testfile));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_futils_find_global_file(&found, testfile));
setenv_and_check("HOME", path.ptr);
cl_git_pass(git_futils_find_global_file(&found, testfile));
cl_setenv("HOME", env_save[0]);
- cl_git_fail(git_futils_find_global_file(&found, testfile));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_futils_find_global_file(&found, testfile));
#ifdef GIT_WIN32
setenv_and_check("HOMEDRIVE", NULL);
@@ -106,7 +108,8 @@ void test_core_env__0(void)
if (root >= 0) {
setenv_and_check("USERPROFILE", NULL);
- cl_git_fail(git_futils_find_global_file(&found, testfile));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_futils_find_global_file(&found, testfile));
old = path.ptr[root];
path.ptr[root] = '\0';
@@ -128,7 +131,8 @@ void test_core_env__1(void)
{
git_buf path = GIT_BUF_INIT;
- cl_must_fail(git_futils_find_global_file(&path, "nonexistentfile"));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_futils_find_global_file(&path, "nonexistentfile"));
cl_git_pass(cl_setenv("HOME", "doesnotexist"));
#ifdef GIT_WIN32
@@ -136,7 +140,8 @@ void test_core_env__1(void)
cl_git_pass(cl_setenv("USERPROFILE", "doesnotexist"));
#endif
- cl_must_fail(git_futils_find_global_file(&path, "nonexistentfile"));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_futils_find_global_file(&path, "nonexistentfile"));
cl_git_pass(cl_setenv("HOME", NULL));
#ifdef GIT_WIN32
@@ -144,13 +149,16 @@ void test_core_env__1(void)
cl_git_pass(cl_setenv("USERPROFILE", NULL));
#endif
- cl_must_fail(git_futils_find_global_file(&path, "nonexistentfile"));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_futils_find_global_file(&path, "nonexistentfile"));
- cl_must_fail(git_futils_find_system_file(&path, "nonexistentfile"));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_futils_find_system_file(&path, "nonexistentfile"));
#ifdef GIT_WIN32
cl_git_pass(cl_setenv("PROGRAMFILES", NULL));
- cl_must_fail(git_futils_find_system_file(&path, "nonexistentfile"));
+ cl_assert_equal_i(
+ GIT_ENOTFOUND, git_futils_find_system_file(&path, "nonexistentfile"));
#endif
git_buf_free(&path);
diff --git a/tests-clar/core/mkdir.c b/tests-clar/core/mkdir.c
index e5dc6654b..1e50b4336 100644
--- a/tests-clar/core/mkdir.c
+++ b/tests-clar/core/mkdir.c
@@ -6,11 +6,11 @@
static void cleanup_basic_dirs(void *ref)
{
GIT_UNUSED(ref);
- git_futils_rmdir_r("d0", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
- git_futils_rmdir_r("d1", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
- git_futils_rmdir_r("d2", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
- git_futils_rmdir_r("d3", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
- git_futils_rmdir_r("d4", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d0", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d1", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d2", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d3", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
}
void test_core_mkdir__basic(void)
@@ -56,7 +56,7 @@ void test_core_mkdir__basic(void)
static void cleanup_basedir(void *ref)
{
GIT_UNUSED(ref);
- git_futils_rmdir_r("base", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("base", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
}
void test_core_mkdir__with_base(void)
@@ -108,7 +108,7 @@ static void cleanup_chmod_root(void *ref)
git__free(mode);
}
- git_futils_rmdir_r("r", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+ git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY);
}
static void check_mode(mode_t expected, mode_t actual)
diff --git a/tests-clar/core/rmdir.c b/tests-clar/core/rmdir.c
index 9ada8f426..f0b0bfa42 100644
--- a/tests-clar/core/rmdir.c
+++ b/tests-clar/core/rmdir.c
@@ -30,7 +30,7 @@ void test_core_rmdir__initialize(void)
/* make sure empty dir can be deleted recusively */
void test_core_rmdir__delete_recursive(void)
{
- cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY));
}
/* make sure non-empty dir cannot be deleted recusively */
@@ -42,15 +42,15 @@ void test_core_rmdir__fail_to_delete_non_empty_dir(void)
cl_git_mkfile(git_buf_cstr(&file), "dummy");
- cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
+ cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY));
cl_must_pass(p_unlink(file.ptr));
- cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY));
git_buf_free(&file);
}
-void test_core_rmdir__can_skip__non_empty_dir(void)
+void test_core_rmdir__can_skip_non_empty_dir(void)
{
git_buf file = GIT_BUF_INIT;
@@ -58,11 +58,41 @@ void test_core_rmdir__can_skip__non_empty_dir(void)
cl_git_mkfile(git_buf_cstr(&file), "dummy");
- cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS));
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_NONEMPTY));
cl_assert(git_path_exists(git_buf_cstr(&file)) == true);
- cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES));
cl_assert(git_path_exists(empty_tmp_dir) == false);
git_buf_free(&file);
}
+
+void test_core_rmdir__can_remove_empty_parents(void)
+{
+ git_buf file = GIT_BUF_INIT;
+
+ cl_git_pass(
+ git_buf_joinpath(&file, empty_tmp_dir, "/one/two_two/three/file.txt"));
+ cl_git_mkfile(git_buf_cstr(&file), "dummy");
+ cl_assert(git_path_isfile(git_buf_cstr(&file)));
+
+ cl_git_pass(git_futils_rmdir_r("one/two_two/three/file.txt", empty_tmp_dir,
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS));
+
+ cl_assert(!git_path_exists(git_buf_cstr(&file)));
+
+ git_buf_rtruncate_at_char(&file, '/'); /* three (only contained file.txt) */
+ cl_assert(!git_path_exists(git_buf_cstr(&file)));
+
+ git_buf_rtruncate_at_char(&file, '/'); /* two_two (only contained three) */
+ cl_assert(!git_path_exists(git_buf_cstr(&file)));
+
+ git_buf_rtruncate_at_char(&file, '/'); /* one (contained two_one also) */
+ cl_assert(git_path_exists(git_buf_cstr(&file)));
+
+ cl_assert(git_path_exists(empty_tmp_dir) == true);
+
+ git_buf_free(&file);
+
+ cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY));
+}
diff --git a/tests-clar/core/stat.c b/tests-clar/core/stat.c
new file mode 100644
index 000000000..2e4abfb79
--- /dev/null
+++ b/tests-clar/core/stat.c
@@ -0,0 +1,97 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "path.h"
+#include "posix.h"
+
+void test_core_stat__initialize(void)
+{
+ cl_git_pass(git_futils_mkdir("root/d1/d2", NULL, 0755, GIT_MKDIR_PATH));
+ cl_git_mkfile("root/file", "whatever\n");
+ cl_git_mkfile("root/d1/file", "whatever\n");
+}
+
+void test_core_stat__cleanup(void)
+{
+ git_futils_rmdir_r("root", NULL, GIT_RMDIR_REMOVE_FILES);
+}
+
+#define cl_assert_error(val) \
+ do { err = errno; cl_assert_equal_i((val), err); } while (0)
+
+void test_core_stat__0(void)
+{
+ struct stat st;
+ int err;
+
+ cl_assert_equal_i(0, p_lstat("root", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/file", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/d1", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/d1/", &st));
+ cl_assert(S_ISDIR(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert_equal_i(0, p_lstat("root/d1/file", &st));
+ cl_assert(S_ISREG(st.st_mode));
+ cl_assert_error(0);
+
+ cl_assert(p_lstat("root/missing", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat("root/missing/but/could/be/created", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat_posixly("root/missing/but/could/be/created", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat("root/d1/missing", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat("root/d1/missing/deeper/path", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat_posixly("root/d1/missing/deeper/path", &st) < 0);
+ cl_assert_error(ENOENT);
+
+ cl_assert(p_lstat_posixly("root/d1/file/deeper/path", &st) < 0);
+ cl_assert_error(ENOTDIR);
+
+ cl_assert(p_lstat("root/file/invalid", &st) < 0);
+#ifdef GIT_WIN32
+ cl_assert_error(ENOENT);
+#else
+ cl_assert_error(ENOTDIR);
+#endif
+
+ cl_assert(p_lstat_posixly("root/file/invalid", &st) < 0);
+ cl_assert_error(ENOTDIR);
+
+ cl_assert(p_lstat("root/file/invalid/deeper_path", &st) < 0);
+#ifdef GIT_WIN32
+ cl_assert_error(ENOENT);
+#else
+ cl_assert_error(ENOTDIR);
+#endif
+
+ cl_assert(p_lstat_posixly("root/file/invalid/deeper_path", &st) < 0);
+ cl_assert_error(ENOTDIR);
+
+ cl_assert(p_lstat_posixly("root/d1/file/extra", &st) < 0);
+ cl_assert_error(ENOTDIR);
+
+ cl_assert(p_lstat_posixly("root/d1/file/further/invalid/items", &st) < 0);
+ cl_assert_error(ENOTDIR);
+}
+
diff --git a/tests-clar/core/vector.c b/tests-clar/core/vector.c
index ef3d6c36d..b165905ae 100644
--- a/tests-clar/core/vector.c
+++ b/tests-clar/core/vector.c
@@ -189,3 +189,87 @@ void test_core_vector__5(void)
git_vector_free(&x);
}
+
+static int remove_ones(git_vector *v, size_t idx)
+{
+ return (git_vector_get(v, idx) == (void *)0x001);
+}
+
+/* Test removal based on callback */
+void test_core_vector__remove_matching(void)
+{
+ git_vector x;
+ size_t i;
+ void *compare;
+
+ git_vector_init(&x, 1, NULL);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 1);
+ git_vector_remove_matching(&x, remove_ones);
+ cl_assert(x.length == 0);
+
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 3);
+ git_vector_remove_matching(&x, remove_ones);
+ cl_assert(x.length == 0);
+
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 4);
+ git_vector_remove_matching(&x, remove_ones);
+ cl_assert(x.length == 2);
+
+ git_vector_foreach(&x, i, compare) {
+ cl_assert(compare != (void *)0x001);
+ }
+
+ git_vector_clear(&x);
+
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 4);
+ git_vector_remove_matching(&x, remove_ones);
+ cl_assert(x.length == 2);
+
+ git_vector_foreach(&x, i, compare) {
+ cl_assert(compare != (void *)0x001);
+ }
+
+ git_vector_clear(&x);
+
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x001);
+
+ cl_assert(x.length == 4);
+ git_vector_remove_matching(&x, remove_ones);
+ cl_assert(x.length == 2);
+
+ git_vector_foreach(&x, i, compare) {
+ cl_assert(compare != (void *)0x001);
+ }
+
+ git_vector_clear(&x);
+
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x003);
+ git_vector_insert(&x, (void*) 0x002);
+ git_vector_insert(&x, (void*) 0x003);
+
+ cl_assert(x.length == 4);
+ git_vector_remove_matching(&x, remove_ones);
+ cl_assert(x.length == 4);
+
+ git_vector_free(&x);
+}
diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c
index d5cf41e99..0a37e829d 100644
--- a/tests-clar/diff/blob.c
+++ b/tests-clar/diff/blob.c
@@ -59,8 +59,8 @@ void test_diff_blob__can_compare_text_blobs(void)
a, b, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_mods);
- cl_assert(expected.at_least_one_of_them_is_binary == false);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, expected.files_binary);
cl_assert_equal_i(1, expected.hunks);
cl_assert_equal_i(6, expected.lines);
@@ -74,8 +74,8 @@ void test_diff_blob__can_compare_text_blobs(void)
b, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_mods);
- cl_assert(expected.at_least_one_of_them_is_binary == false);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, expected.files_binary);
cl_assert_equal_i(1, expected.hunks);
cl_assert_equal_i(15, expected.lines);
@@ -89,8 +89,8 @@ void test_diff_blob__can_compare_text_blobs(void)
a, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_mods);
- cl_assert(expected.at_least_one_of_them_is_binary == false);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, expected.files_binary);
cl_assert_equal_i(1, expected.hunks);
cl_assert_equal_i(13, expected.lines);
@@ -103,8 +103,8 @@ void test_diff_blob__can_compare_text_blobs(void)
c, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_mods);
- cl_assert(expected.at_least_one_of_them_is_binary == false);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, expected.files_binary);
cl_assert_equal_i(2, expected.hunks);
cl_assert_equal_i(14, expected.lines);
@@ -125,8 +125,8 @@ void test_diff_blob__can_compare_against_null_blobs(void)
d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_dels);
- cl_assert(expected.at_least_one_of_them_is_binary == false);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(0, expected.files_binary);
cl_assert_equal_i(1, expected.hunks);
cl_assert_equal_i(14, expected.hunk_old_lines);
@@ -140,8 +140,8 @@ void test_diff_blob__can_compare_against_null_blobs(void)
d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_adds);
- cl_assert(expected.at_least_one_of_them_is_binary == false);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, expected.files_binary);
cl_assert_equal_i(1, expected.hunks);
cl_assert_equal_i(14, expected.hunk_new_lines);
@@ -154,10 +154,9 @@ void test_diff_blob__can_compare_against_null_blobs(void)
cl_git_pass(git_diff_blobs(
alien, NULL, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- cl_assert(expected.at_least_one_of_them_is_binary == true);
-
cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_dels);
+ cl_assert_equal_i(1, expected.files_binary);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]);
cl_assert_equal_i(0, expected.hunks);
cl_assert_equal_i(0, expected.lines);
@@ -166,20 +165,19 @@ void test_diff_blob__can_compare_against_null_blobs(void)
cl_git_pass(git_diff_blobs(
NULL, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- cl_assert(expected.at_least_one_of_them_is_binary == true);
-
cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_adds);
+ cl_assert_equal_i(1, expected.files_binary);
+ cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]);
cl_assert_equal_i(0, expected.hunks);
cl_assert_equal_i(0, expected.lines);
}
-static void assert_identical_blobs_comparison(diff_expects expected)
+static void assert_identical_blobs_comparison(diff_expects *expected)
{
- cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_unmodified);
- cl_assert_equal_i(0, expected.hunks);
- cl_assert_equal_i(0, expected.lines);
+ cl_assert_equal_i(1, expected->files);
+ cl_assert_equal_i(1, expected->file_status[GIT_DELTA_UNMODIFIED]);
+ cl_assert_equal_i(0, expected->hunks);
+ cl_assert_equal_i(0, expected->lines);
}
void test_diff_blob__can_compare_identical_blobs(void)
@@ -187,32 +185,32 @@ void test_diff_blob__can_compare_identical_blobs(void)
cl_git_pass(git_diff_blobs(
d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- cl_assert(expected.at_least_one_of_them_is_binary == false);
- assert_identical_blobs_comparison(expected);
+ cl_assert_equal_i(0, expected.files_binary);
+ assert_identical_blobs_comparison(&expected);
memset(&expected, 0, sizeof(expected));
cl_git_pass(git_diff_blobs(
NULL, NULL, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- cl_assert(expected.at_least_one_of_them_is_binary == false);
- assert_identical_blobs_comparison(expected);
+ cl_assert_equal_i(0, expected.files_binary);
+ assert_identical_blobs_comparison(&expected);
memset(&expected, 0, sizeof(expected));
cl_git_pass(git_diff_blobs(
alien, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- cl_assert(expected.at_least_one_of_them_is_binary == true);
- assert_identical_blobs_comparison(expected);
+ cl_assert(expected.files_binary > 0);
+ assert_identical_blobs_comparison(&expected);
}
-static void assert_binary_blobs_comparison(diff_expects expected)
+static void assert_binary_blobs_comparison(diff_expects *expected)
{
- cl_assert(expected.at_least_one_of_them_is_binary == true);
+ cl_assert(expected->files_binary > 0);
- cl_assert_equal_i(1, expected.files);
- cl_assert_equal_i(1, expected.file_mods);
- cl_assert_equal_i(0, expected.hunks);
- cl_assert_equal_i(0, expected.lines);
+ cl_assert_equal_i(1, expected->files);
+ cl_assert_equal_i(1, expected->file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, expected->hunks);
+ cl_assert_equal_i(0, expected->lines);
}
void test_diff_blob__can_compare_two_binary_blobs(void)
@@ -227,14 +225,14 @@ void test_diff_blob__can_compare_two_binary_blobs(void)
cl_git_pass(git_diff_blobs(
alien, heart, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- assert_binary_blobs_comparison(expected);
+ assert_binary_blobs_comparison(&expected);
memset(&expected, 0, sizeof(expected));
cl_git_pass(git_diff_blobs(
heart, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- assert_binary_blobs_comparison(expected);
+ assert_binary_blobs_comparison(&expected);
git_blob_free(heart);
}
@@ -244,14 +242,14 @@ void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void)
cl_git_pass(git_diff_blobs(
alien, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- assert_binary_blobs_comparison(expected);
+ assert_binary_blobs_comparison(&expected);
memset(&expected, 0, sizeof(expected));
cl_git_pass(git_diff_blobs(
d, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn));
- assert_binary_blobs_comparison(expected);
+ assert_binary_blobs_comparison(&expected);
}
/*
diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c
index de0e7e074..992c87d4c 100644
--- a/tests-clar/diff/diff_helpers.c
+++ b/tests-clar/diff/diff_helpers.c
@@ -32,20 +32,13 @@ int diff_file_fn(
e->files++;
- if (delta->binary) {
- e->at_least_one_of_them_is_binary = true;
+ if (delta->binary)
e->files_binary++;
- }
- switch (delta->status) {
- case GIT_DELTA_ADDED: e->file_adds++; break;
- case GIT_DELTA_DELETED: e->file_dels++; break;
- case GIT_DELTA_MODIFIED: e->file_mods++; break;
- case GIT_DELTA_IGNORED: e->file_ignored++; break;
- case GIT_DELTA_UNTRACKED: e->file_untracked++; break;
- case GIT_DELTA_UNMODIFIED: e->file_unmodified++; break;
- default: break;
- }
+ cl_assert(delta->status <= GIT_DELTA_TYPECHANGE);
+
+ e->file_status[delta->status] += 1;
+
return 0;
}
diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h
index 629130934..6ff493d49 100644
--- a/tests-clar/diff/diff_helpers.h
+++ b/tests-clar/diff/diff_helpers.h
@@ -8,12 +8,7 @@ typedef struct {
int files;
int files_binary;
- int file_adds;
- int file_dels;
- int file_mods;
- int file_ignored;
- int file_untracked;
- int file_unmodified;
+ int file_status[10]; /* indexed by git_delta_t value */
int hunks;
int hunk_new_lines;
@@ -23,8 +18,6 @@ typedef struct {
int line_ctxt;
int line_adds;
int line_dels;
-
- bool at_least_one_of_them_is_binary;
} diff_expects;
extern int diff_file_fn(
diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c
index f6d9bfc38..133392c21 100644
--- a/tests-clar/diff/diffiter.c
+++ b/tests-clar/diff/diffiter.c
@@ -16,7 +16,7 @@ void test_diff_diffiter__create(void)
git_diff_list *diff;
size_t d, num_d;
- cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, NULL));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
@@ -34,7 +34,7 @@ void test_diff_diffiter__iterate_files(void)
size_t d, num_d;
int count = 0;
- cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, NULL));
num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(6, (int)num_d);
@@ -57,7 +57,7 @@ void test_diff_diffiter__iterate_files_2(void)
size_t d, num_d;
int count = 0;
- cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, NULL));
num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(8, (int)num_d);
@@ -85,7 +85,7 @@ void test_diff_diffiter__iterate_files_and_hunks(void)
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
- cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
@@ -138,7 +138,7 @@ void test_diff_diffiter__max_size_threshold(void)
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
- cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
@@ -173,7 +173,7 @@ void test_diff_diffiter__max_size_threshold(void)
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
opts.max_size = 50; /* treat anything over 50 bytes as binary! */
- cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
@@ -216,7 +216,7 @@ void test_diff_diffiter__iterate_all(void)
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
- cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
@@ -292,7 +292,7 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void)
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
- cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts));
num_d = git_diff_num_deltas(diff);
@@ -342,3 +342,102 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void)
cl_assert_equal_i(8, exp.hunks);
cl_assert_equal_i(14, exp.lines);
}
+
+/* This output is taken directly from `git diff` on the status test data */
+static const char *expected_patch_text[8] = {
+ /* 0 */
+ "diff --git a/file_deleted b/file_deleted\n"
+ "deleted file mode 100644\n"
+ "index 5452d32..0000000\n"
+ "--- a/file_deleted\n"
+ "+++ /dev/null\n"
+ "@@ -1 +0,0 @@\n"
+ "-file_deleted\n",
+ /* 1 */
+ "diff --git a/modified_file b/modified_file\n"
+ "index 452e424..0a53963 100644\n"
+ "--- a/modified_file\n"
+ "+++ b/modified_file\n"
+ "@@ -1 +1,2 @@\n"
+ " modified_file\n"
+ "+modified_file\n",
+ /* 2 */
+ "diff --git a/staged_changes_file_deleted b/staged_changes_file_deleted\n"
+ "deleted file mode 100644\n"
+ "index a6be623..0000000\n"
+ "--- a/staged_changes_file_deleted\n"
+ "+++ /dev/null\n"
+ "@@ -1,2 +0,0 @@\n"
+ "-staged_changes_file_deleted\n"
+ "-staged_changes_file_deleted\n",
+ /* 3 */
+ "diff --git a/staged_changes_modified_file b/staged_changes_modified_file\n"
+ "index 906ee77..011c344 100644\n"
+ "--- a/staged_changes_modified_file\n"
+ "+++ b/staged_changes_modified_file\n"
+ "@@ -1,2 +1,3 @@\n"
+ " staged_changes_modified_file\n"
+ " staged_changes_modified_file\n"
+ "+staged_changes_modified_file\n",
+ /* 4 */
+ "diff --git a/staged_new_file_deleted_file b/staged_new_file_deleted_file\n"
+ "deleted file mode 100644\n"
+ "index 90b8c29..0000000\n"
+ "--- a/staged_new_file_deleted_file\n"
+ "+++ /dev/null\n"
+ "@@ -1 +0,0 @@\n"
+ "-staged_new_file_deleted_file\n",
+ /* 5 */
+ "diff --git a/staged_new_file_modified_file b/staged_new_file_modified_file\n"
+ "index ed06290..8b090c0 100644\n"
+ "--- a/staged_new_file_modified_file\n"
+ "+++ b/staged_new_file_modified_file\n"
+ "@@ -1 +1,2 @@\n"
+ " staged_new_file_modified_file\n"
+ "+staged_new_file_modified_file\n",
+ /* 6 */
+ "diff --git a/subdir/deleted_file b/subdir/deleted_file\n"
+ "deleted file mode 100644\n"
+ "index 1888c80..0000000\n"
+ "--- a/subdir/deleted_file\n"
+ "+++ /dev/null\n"
+ "@@ -1 +0,0 @@\n"
+ "-subdir/deleted_file\n",
+ /* 7 */
+ "diff --git a/subdir/modified_file b/subdir/modified_file\n"
+ "index a619198..57274b7 100644\n"
+ "--- a/subdir/modified_file\n"
+ "+++ b/subdir/modified_file\n"
+ "@@ -1 +1,2 @@\n"
+ " subdir/modified_file\n"
+ "+subdir/modified_file\n"
+};
+
+void test_diff_diffiter__iterate_and_generate_patch_text(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff_list *diff;
+ size_t d, num_d;
+
+ cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, NULL));
+
+ num_d = git_diff_num_deltas(diff);
+ cl_assert_equal_i(8, (int)num_d);
+
+ for (d = 0; d < num_d; ++d) {
+ git_diff_patch *patch;
+ char *text;
+
+ cl_git_pass(git_diff_get_patch(&patch, NULL, diff, d));
+ cl_assert(patch != NULL);
+
+ cl_git_pass(git_diff_patch_to_str(&text, patch));
+
+ cl_assert_equal_s(expected_patch_text[d], text);
+
+ git__free(text);
+ git_diff_patch_free(patch);
+ }
+
+ git_diff_list_free(diff);
+}
diff --git a/tests-clar/diff/index.c b/tests-clar/diff/index.c
index 7c4bddb90..4b96bfa09 100644
--- a/tests-clar/diff/index.c
+++ b/tests-clar/diff/index.c
@@ -32,7 +32,7 @@ void test_diff_index__0(void)
memset(&exp, 0, sizeof(exp));
- cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff));
+ cl_git_pass(git_diff_index_to_tree(&diff, g_repo, a, NULL, &opts));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
@@ -45,9 +45,9 @@ void test_diff_index__0(void)
* - mv .git .gitted
*/
cl_assert_equal_i(8, exp.files);
- cl_assert_equal_i(3, exp.file_adds);
- cl_assert_equal_i(2, exp.file_dels);
- cl_assert_equal_i(3, exp.file_mods);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(8, exp.hunks);
@@ -60,7 +60,7 @@ void test_diff_index__0(void)
diff = NULL;
memset(&exp, 0, sizeof(exp));
- cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff));
+ cl_git_pass(git_diff_index_to_tree(&diff, g_repo, b, NULL, &opts));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
@@ -73,9 +73,9 @@ void test_diff_index__0(void)
* - mv .git .gitted
*/
cl_assert_equal_i(12, exp.files);
- cl_assert_equal_i(7, exp.file_adds);
- cl_assert_equal_i(2, exp.file_dels);
- cl_assert_equal_i(3, exp.file_mods);
+ cl_assert_equal_i(7, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(12, exp.hunks);
@@ -125,7 +125,7 @@ void test_diff_index__1(void)
memset(&exp, 0, sizeof(exp));
- cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff));
+ cl_git_pass(git_diff_index_to_tree(&diff, g_repo, a, NULL, &opts));
cl_assert_equal_i(
GIT_EUSER,
diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c
index c2ab9940b..368903200 100644
--- a/tests-clar/diff/iterator.c
+++ b/tests-clar/diff/iterator.c
@@ -350,7 +350,7 @@ static void index_iterator_test(
int count = 0;
git_repository *repo = cl_git_sandbox_init(sandbox);
- cl_git_pass(git_iterator_for_index_range(&i, repo, start, end));
+ cl_git_pass(git_iterator_for_repo_index_range(&i, repo, start, end));
cl_git_pass(git_iterator_current(i, &entry));
while (entry != NULL) {
diff --git a/tests-clar/diff/patch.c b/tests-clar/diff/patch.c
index e8468386c..6aaf7651f 100644
--- a/tests-clar/diff/patch.c
+++ b/tests-clar/diff/patch.c
@@ -88,7 +88,7 @@ void test_diff_patch__can_properly_display_the_removal_of_a_file(void)
one = resolve_commit_oid_to_tree(g_repo, one_sha);
another = resolve_commit_oid_to_tree(g_repo, another_sha);
- cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, one, another, &diff));
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL));
cl_git_pass(git_diff_print_patch(diff, NULL, check_removal_cb));
@@ -97,3 +97,33 @@ void test_diff_patch__can_properly_display_the_removal_of_a_file(void)
git_tree_free(another);
git_tree_free(one);
}
+
+void test_diff_patch__to_string(void)
+{
+ const char *one_sha = "26a125e";
+ const char *another_sha = "735b6a2";
+ git_tree *one, *another;
+ git_diff_list *diff;
+ git_diff_patch *patch;
+ char *text;
+ const char *expected = "diff --git a/subdir.txt b/subdir.txt\ndeleted file mode 100644\nindex e8ee89e..0000000\n--- a/subdir.txt\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-Is it a bird?\n-Is it a plane?\n";
+
+ one = resolve_commit_oid_to_tree(g_repo, one_sha);
+ another = resolve_commit_oid_to_tree(g_repo, another_sha);
+
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL));
+
+ cl_assert_equal_i(1, git_diff_num_deltas(diff));
+
+ cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0));
+
+ cl_git_pass(git_diff_patch_to_str(&text, patch));
+
+ cl_assert_equal_s(expected, text);
+
+ git__free(text);
+ git_diff_patch_free(patch);
+ git_diff_list_free(diff);
+ git_tree_free(another);
+ git_tree_free(one);
+}
diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c
new file mode 100644
index 000000000..1ea2e3fc9
--- /dev/null
+++ b/tests-clar/diff/rename.c
@@ -0,0 +1,105 @@
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+static git_repository *g_repo = NULL;
+
+void test_diff_rename__initialize(void)
+{
+ g_repo = cl_git_sandbox_init("renames");
+}
+
+void test_diff_rename__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+/*
+ * Renames repo has:
+ *
+ * commit 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 -
+ * serving.txt (25 lines)
+ * sevencities.txt (50 lines)
+ * commit 2bc7f351d20b53f1c72c16c4b036e491c478c49a -
+ * serving.txt -> sixserving.txt (rename, no change, 100% match)
+ * sevencities.txt -> sevencities.txt (no change)
+ * sevencities.txt -> songofseven.txt (copy, no change, 100% match)
+ *
+ * TODO: add commits with various % changes of copy / rename
+ */
+
+void test_diff_rename__match_oid(void)
+{
+ const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2";
+ const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ git_tree *old_tree, *new_tree;
+ git_diff_list *diff;
+ git_diff_options diffopts = {0};
+ git_diff_find_options opts;
+ diff_expects exp;
+
+ old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
+ new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
+
+ /* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate
+ * --find-copies-harder during rename transformion...
+ */
+ memset(&diffopts, 0, sizeof(diffopts));
+ diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ /* git diff --no-renames \
+ * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
+ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a
+ */
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+ cl_assert_equal_i(4, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+
+ /* git diff 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
+ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a
+ */
+ cl_git_pass(git_diff_find_similar(diff, NULL));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+ cl_assert_equal_i(3, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_list_free(diff);
+
+ cl_git_pass(git_diff_tree_to_tree(
+ &diff, g_repo, old_tree, new_tree, &diffopts));
+
+ /* git diff --find-copies-harder \
+ * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
+ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a
+ */
+ memset(&opts, 0, sizeof(opts));
+ opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
+ cl_git_pass(git_diff_find_similar(diff, &opts));
+
+ memset(&exp, 0, sizeof(exp));
+ cl_git_pass(git_diff_foreach(
+ diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+ cl_assert_equal_i(3, exp.files);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]);
+
+ git_diff_list_free(diff);
+
+ git_tree_free(old_tree);
+ git_tree_free(new_tree);
+}
diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c
index c5a0e626e..8e8939976 100644
--- a/tests-clar/diff/tree.c
+++ b/tests-clar/diff/tree.c
@@ -34,15 +34,15 @@ void test_diff_tree__0(void)
memset(&exp, 0, sizeof(exp));
- cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff));
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(5, exp.files);
- cl_assert_equal_i(2, exp.file_adds);
- cl_assert_equal_i(1, exp.file_dels);
- cl_assert_equal_i(2, exp.file_mods);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(5, exp.hunks);
@@ -56,15 +56,15 @@ void test_diff_tree__0(void)
memset(&exp, 0, sizeof(exp));
- cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, b, &diff));
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, b, &opts));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(2, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(2, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(2, exp.hunks);
@@ -111,22 +111,23 @@ void test_diff_tree__options(void)
* - git diff [options] 6bab5c79cd5140d0 605812ab7fe421fdd
* - mv .git .gitted
*/
+#define EXPECT_STATUS_ADM(ADDS,DELS,MODS) { 0, ADDS, DELS, MODS, 0, 0, 0, 0, 0 }
diff_expects test_expects[] = {
/* a vs b tests */
- { 5, 0, 3, 0, 2, 0, 0, 0, 4, 0, 0, 51, 2, 46, 3 },
- { 5, 0, 3, 0, 2, 0, 0, 0, 4, 0, 0, 53, 4, 46, 3 },
- { 5, 0, 0, 3, 2, 0, 0, 0, 4, 0, 0, 52, 3, 3, 46 },
- { 5, 0, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 47, 4 },
+ { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 4, 0, 0, 51, 2, 46, 3 },
+ { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 4, 0, 0, 53, 4, 46, 3 },
+ { 5, 0, EXPECT_STATUS_ADM(0, 3, 2), 4, 0, 0, 52, 3, 3, 46 },
+ { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 5, 0, 0, 54, 3, 47, 4 },
/* c vs d tests */
- { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 22, 9, 10, 3 },
- { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 19, 12, 7, 0 },
- { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 20, 11, 8, 1 },
- { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 20, 11, 8, 1 },
- { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 18, 11, 0, 7 },
+ { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 22, 9, 10, 3 },
+ { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 19, 12, 7, 0 },
+ { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 20, 11, 8, 1 },
+ { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 20, 11, 8, 1 },
+ { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 18, 11, 0, 7 },
{ 0 },
};
diff_expects *expected;
- int i;
+ int i, j;
g_repo = cl_git_sandbox_init("attr");
@@ -140,18 +141,17 @@ void test_diff_tree__options(void)
opts = test_options[i];
if (test_ab_or_cd[i] == 0)
- cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff));
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
else
- cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, d, &diff));
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, d, &opts));
cl_git_pass(git_diff_foreach(
diff, &actual, diff_file_fn, diff_hunk_fn, diff_line_fn));
expected = &test_expects[i];
cl_assert_equal_i(actual.files, expected->files);
- cl_assert_equal_i(actual.file_adds, expected->file_adds);
- cl_assert_equal_i(actual.file_dels, expected->file_dels);
- cl_assert_equal_i(actual.file_mods, expected->file_mods);
+ for (j = GIT_DELTA_UNMODIFIED; j <= GIT_DELTA_TYPECHANGE; ++j)
+ cl_assert_equal_i(expected->file_status[j], actual.file_status[j]);
cl_assert_equal_i(actual.hunks, expected->hunks);
cl_assert_equal_i(actual.lines, expected->lines);
cl_assert_equal_i(actual.line_ctxt, expected->line_ctxt);
@@ -187,15 +187,15 @@ void test_diff_tree__bare(void)
memset(&exp, 0, sizeof(exp));
- cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff));
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(3, exp.files);
- cl_assert_equal_i(2, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(3, exp.hunks);
@@ -225,9 +225,9 @@ void test_diff_tree__merge(void)
cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL);
cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL);
- cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, a, b, &diff1));
+ cl_git_pass(git_diff_tree_to_tree(&diff1, g_repo, a, b, NULL));
- cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, c, b, &diff2));
+ cl_git_pass(git_diff_tree_to_tree(&diff2, g_repo, c, b, NULL));
git_tree_free(a);
git_tree_free(b);
@@ -243,9 +243,9 @@ void test_diff_tree__merge(void)
diff1, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(6, exp.files);
- cl_assert_equal_i(2, exp.file_adds);
- cl_assert_equal_i(1, exp.file_dels);
- cl_assert_equal_i(3, exp.file_mods);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(6, exp.hunks);
@@ -279,7 +279,7 @@ void test_diff_tree__larger_hunks(void)
opts.context_lines = 1;
opts.interhunk_lines = 0;
- cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff));
+ cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
num_d = git_diff_num_deltas(diff);
for (d = 0; d < num_d; ++d) {
diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c
index 3e388ea70..a4dbe37ff 100644
--- a/tests-clar/diff/workdir.c
+++ b/tests-clar/diff/workdir.c
@@ -26,7 +26,7 @@ void test_diff_workdir__to_index(void)
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -46,11 +46,11 @@ void test_diff_workdir__to_index(void)
* - mv .git .gitted
*/
cl_assert_equal_i(13, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(4, exp.file_dels);
- cl_assert_equal_i(4, exp.file_mods);
- cl_assert_equal_i(1, exp.file_ignored);
- cl_assert_equal_i(4, exp.file_untracked);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]);
cl_assert_equal_i(8, exp.hunks);
@@ -94,7 +94,7 @@ void test_diff_workdir__to_tree(void)
* The results are documented at the bottom of this file in the
* long comment entitled "PREPARATION OF TEST DATA".
*/
- cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff));
+ cl_git_pass(git_diff_workdir_to_tree(&diff, g_repo, a, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -107,11 +107,11 @@ void test_diff_workdir__to_tree(void)
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(14, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(4, exp.file_dels);
- cl_assert_equal_i(4, exp.file_mods);
- cl_assert_equal_i(1, exp.file_ignored);
- cl_assert_equal_i(5, exp.file_untracked);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNTRACKED]);
}
/* Since there is no git diff equivalent, let's just assume that the
@@ -127,8 +127,8 @@ void test_diff_workdir__to_tree(void)
* a workdir to tree diff (even though it is not really). This is what
* you would get from "git diff --name-status 26a125ee1bf"
*/
- cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff));
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2));
+ cl_git_pass(git_diff_index_to_tree(&diff, g_repo, a, NULL, &opts));
+ cl_git_pass(git_diff_workdir_to_index(&diff2, g_repo, NULL, &opts));
cl_git_pass(git_diff_merge(diff, diff2));
git_diff_list_free(diff2);
@@ -143,11 +143,11 @@ void test_diff_workdir__to_tree(void)
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(15, exp.files);
- cl_assert_equal_i(2, exp.file_adds);
- cl_assert_equal_i(5, exp.file_dels);
- cl_assert_equal_i(4, exp.file_mods);
- cl_assert_equal_i(1, exp.file_ignored);
- cl_assert_equal_i(3, exp.file_untracked);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
cl_assert_equal_i(11, exp.hunks);
@@ -164,8 +164,8 @@ void test_diff_workdir__to_tree(void)
/* Again, emulating "git diff <sha>" for testing purposes using
* "git diff --name-status 0017bd4ab1ec3" instead.
*/
- cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff));
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2));
+ cl_git_pass(git_diff_index_to_tree(&diff, g_repo, b, NULL, &opts));
+ cl_git_pass(git_diff_workdir_to_index(&diff2, g_repo, NULL, &opts));
cl_git_pass(git_diff_merge(diff, diff2));
git_diff_list_free(diff2);
@@ -180,11 +180,11 @@ void test_diff_workdir__to_tree(void)
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(16, exp.files);
- cl_assert_equal_i(5, exp.file_adds);
- cl_assert_equal_i(4, exp.file_dels);
- cl_assert_equal_i(3, exp.file_mods);
- cl_assert_equal_i(1, exp.file_ignored);
- cl_assert_equal_i(3, exp.file_untracked);
+ cl_assert_equal_i(5, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]);
cl_assert_equal_i(12, exp.hunks);
@@ -216,7 +216,7 @@ void test_diff_workdir__to_index_with_pathspec(void)
opts.pathspec.strings = &pathspec;
opts.pathspec.count = 1;
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -228,18 +228,18 @@ void test_diff_workdir__to_index_with_pathspec(void)
cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL));
cl_assert_equal_i(13, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(4, exp.file_dels);
- cl_assert_equal_i(4, exp.file_mods);
- cl_assert_equal_i(1, exp.file_ignored);
- cl_assert_equal_i(4, exp.file_untracked);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]);
}
git_diff_list_free(diff);
pathspec = "modified_file";
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -251,18 +251,18 @@ void test_diff_workdir__to_index_with_pathspec(void)
cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL));
cl_assert_equal_i(1, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
- cl_assert_equal_i(0, exp.file_ignored);
- cl_assert_equal_i(0, exp.file_untracked);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]);
}
git_diff_list_free(diff);
pathspec = "subdir";
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -274,18 +274,18 @@ void test_diff_workdir__to_index_with_pathspec(void)
cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL));
cl_assert_equal_i(3, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(1, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
- cl_assert_equal_i(0, exp.file_ignored);
- cl_assert_equal_i(1, exp.file_untracked);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
}
git_diff_list_free(diff);
pathspec = "*_deleted";
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -297,11 +297,11 @@ void test_diff_workdir__to_index_with_pathspec(void)
cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL));
cl_assert_equal_i(2, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(2, exp.file_dels);
- cl_assert_equal_i(0, exp.file_mods);
- cl_assert_equal_i(0, exp.file_ignored);
- cl_assert_equal_i(0, exp.file_untracked);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]);
}
git_diff_list_free(diff);
@@ -324,7 +324,7 @@ void test_diff_workdir__filemode_changes(void)
/* test once with no mods */
- cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, NULL));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -337,7 +337,7 @@ void test_diff_workdir__filemode_changes(void)
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(0, exp.files);
- cl_assert_equal_i(0, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(0, exp.hunks);
}
@@ -347,7 +347,7 @@ void test_diff_workdir__filemode_changes(void)
cl_assert(cl_toggle_filemode("issue_592/a.txt"));
- cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, NULL));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -360,7 +360,7 @@ void test_diff_workdir__filemode_changes(void)
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, exp.files);
- cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(0, exp.hunks);
}
@@ -386,14 +386,14 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void)
/* test once with no mods */
- cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, NULL));
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(0, exp.files);
- cl_assert_equal_i(0, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(0, exp.hunks);
git_diff_list_free(diff);
@@ -402,14 +402,14 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void)
cl_assert(cl_toggle_filemode("issue_592/a.txt"));
- cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, NULL));
memset(&exp, 0, sizeof(exp));
cl_git_pass(git_diff_foreach(
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(0, exp.files);
- cl_assert_equal_i(0, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(0, exp.hunks);
git_diff_list_free(diff);
@@ -442,8 +442,8 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void)
opts.pathspec.strings = &pathspec;
opts.pathspec.count = 1;
- cl_git_pass(git_diff_index_to_tree(g_repo, &opts, tree, &diff_i2t));
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff_w2i));
+ cl_git_pass(git_diff_index_to_tree(&diff_i2t, g_repo, tree, NULL, &opts));
+ cl_git_pass(git_diff_workdir_to_index(&diff_w2i, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -456,9 +456,9 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void)
diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(1, exp.hunks);
cl_assert_equal_i(2, exp.lines);
cl_assert_equal_i(1, exp.line_ctxt);
@@ -477,9 +477,9 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void)
diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(1, exp.hunks);
cl_assert_equal_i(3, exp.lines);
cl_assert_equal_i(2, exp.line_ctxt);
@@ -500,9 +500,9 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void)
diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(1, exp.hunks);
cl_assert_equal_i(3, exp.lines);
cl_assert_equal_i(1, exp.line_ctxt);
@@ -529,7 +529,7 @@ void test_diff_workdir__eof_newline_changes(void)
opts.pathspec.strings = &pathspec;
opts.pathspec.count = 1;
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -542,9 +542,9 @@ void test_diff_workdir__eof_newline_changes(void)
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(0, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(0, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(0, exp.hunks);
cl_assert_equal_i(0, exp.lines);
cl_assert_equal_i(0, exp.line_ctxt);
@@ -556,7 +556,7 @@ void test_diff_workdir__eof_newline_changes(void)
cl_git_append2file("status/current_file", "\n");
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -569,9 +569,9 @@ void test_diff_workdir__eof_newline_changes(void)
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(1, exp.hunks);
cl_assert_equal_i(2, exp.lines);
cl_assert_equal_i(1, exp.line_ctxt);
@@ -583,7 +583,7 @@ void test_diff_workdir__eof_newline_changes(void)
cl_git_rewritefile("status/current_file", "current_file");
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
memset(&exp, 0, sizeof(exp));
@@ -596,9 +596,9 @@ void test_diff_workdir__eof_newline_changes(void)
diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
cl_assert_equal_i(1, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
cl_assert_equal_i(1, exp.hunks);
cl_assert_equal_i(3, exp.lines);
cl_assert_equal_i(0, exp.line_ctxt);
@@ -699,13 +699,13 @@ void test_diff_workdir__larger_hunks(void)
/* okay, this is a bit silly, but oh well */
switch (i) {
case 0:
- cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
break;
case 1:
- cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff));
+ cl_git_pass(git_diff_workdir_to_tree(&diff, g_repo, a, &opts));
break;
case 2:
- cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, b, &diff));
+ cl_git_pass(git_diff_workdir_to_tree(&diff, g_repo, b, &opts));
break;
}
@@ -784,7 +784,7 @@ void test_diff_workdir__submodules(void)
GIT_DIFF_RECURSE_UNTRACKED_DIRS |
GIT_DIFF_INCLUDE_UNTRACKED_CONTENT;
- cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff));
+ cl_git_pass(git_diff_workdir_to_tree(&diff, g_repo, a, &opts));
/* diff_print(stderr, diff); */
@@ -801,11 +801,11 @@ void test_diff_workdir__submodules(void)
cl_assert_equal_i(10, exp.files);
- cl_assert_equal_i(0, exp.file_adds);
- cl_assert_equal_i(0, exp.file_dels);
- cl_assert_equal_i(1, exp.file_mods);
- cl_assert_equal_i(0, exp.file_ignored);
- cl_assert_equal_i(9, exp.file_untracked);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+ cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+ cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+ cl_assert_equal_i(9, exp.file_status[GIT_DELTA_UNTRACKED]);
/* the following numbers match "git diff 873585" exactly */
@@ -828,10 +828,13 @@ void test_diff_workdir__cannot_diff_against_a_bare_repository(void)
g_repo = cl_git_sandbox_init("testrepo.git");
- cl_assert_equal_i(GIT_EBAREREPO, git_diff_workdir_to_index(g_repo, &opts, &diff));
+ cl_assert_equal_i(
+ GIT_EBAREREPO, git_diff_workdir_to_index(&diff, g_repo, NULL, &opts));
cl_git_pass(git_repository_head_tree(&tree, g_repo));
- cl_assert_equal_i(GIT_EBAREREPO, git_diff_workdir_to_tree(g_repo, &opts, tree, &diff));
+
+ cl_assert_equal_i(
+ GIT_EBAREREPO, git_diff_workdir_to_tree(&diff, g_repo, tree, &opts));
git_tree_free(tree);
}
diff --git a/tests-clar/fetchhead/fetchhead_data.h b/tests-clar/fetchhead/fetchhead_data.h
new file mode 100644
index 000000000..71f67be25
--- /dev/null
+++ b/tests-clar/fetchhead/fetchhead_data.h
@@ -0,0 +1,21 @@
+
+#define FETCH_HEAD_WILDCARD_DATA \
+ "49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
+ "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
+ "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
+ "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \
+ "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \
+ "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n"
+
+#define FETCH_HEAD_NO_MERGE_DATA \
+ "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \
+ "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \
+ "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \
+ "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \
+ "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \
+ "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n"
+
+
+#define FETCH_HEAD_EXPLICIT_DATA \
+ "0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n"
+
diff --git a/tests-clar/fetchhead/network.c b/tests-clar/fetchhead/network.c
new file mode 100644
index 000000000..ef9d01bf9
--- /dev/null
+++ b/tests-clar/fetchhead/network.c
@@ -0,0 +1,87 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "fetchhead.h"
+#include "fetchhead_data.h"
+#include "git2/clone.h"
+
+CL_IN_CATEGORY("network")
+
+#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository"
+
+static git_repository *g_repo;
+
+void test_fetchhead_network__initialize(void)
+{
+ g_repo = NULL;
+}
+
+static void cleanup_repository(void *path)
+{
+ if (g_repo)
+ git_repository_free(g_repo);
+ cl_fixture_cleanup((const char *)path);
+}
+
+
+static void fetchhead_test_clone(void)
+{
+ cl_set_cleanup(&cleanup_repository, "./test1");
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./test1", NULL, NULL, NULL));
+}
+
+static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead)
+{
+ git_remote *remote;
+ git_buf fetchhead_buf = GIT_BUF_INIT;
+ int equals = 0;
+
+ cl_git_pass(git_remote_load(&remote, g_repo, "origin"));
+ git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_AUTO);
+
+ if(fetchspec != NULL)
+ git_remote_set_fetchspec(remote, fetchspec);
+
+ cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH));
+ cl_git_pass(git_remote_download(remote, NULL, NULL));
+ git_remote_disconnect(remote);
+
+ cl_git_pass(git_remote_update_tips(remote));
+ git_remote_free(remote);
+
+ cl_git_pass(git_futils_readbuffer(&fetchhead_buf,
+ "./test1/.git/FETCH_HEAD"));
+
+ equals = (strcmp(fetchhead_buf.ptr, expected_fetchhead) == 0);
+
+ git_buf_free(&fetchhead_buf);
+
+ cl_assert(equals);
+}
+
+void test_fetchhead_network__wildcard_spec(void)
+{
+ fetchhead_test_clone();
+ fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA);
+}
+
+void test_fetchhead_network__explicit_spec(void)
+{
+ fetchhead_test_clone();
+ fetchhead_test_fetch("refs/heads/first-merge:refs/remotes/origin/first-merge", FETCH_HEAD_EXPLICIT_DATA);
+}
+
+void test_fetchhead_network__no_merges(void)
+{
+ git_config *config;
+
+ fetchhead_test_clone();
+
+ cl_git_pass(git_repository_config(&config, g_repo));
+ cl_git_pass(git_config_set_string(config, "branch.master.remote", NULL));
+ cl_git_pass(git_config_set_string(config, "branch.master.merge", NULL));
+ git_config_free(config);
+
+ fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA);
+}
diff --git a/tests-clar/fetchhead/nonetwork.c b/tests-clar/fetchhead/nonetwork.c
new file mode 100644
index 000000000..1de5280a8
--- /dev/null
+++ b/tests-clar/fetchhead/nonetwork.c
@@ -0,0 +1,96 @@
+#include "clar_libgit2.h"
+
+#include "repository.h"
+#include "fetchhead.h"
+#include "fetchhead_data.h"
+
+#define DO_LOCAL_TEST 0
+
+static git_repository *g_repo;
+
+void test_fetchhead_nonetwork__initialize(void)
+{
+ g_repo = NULL;
+}
+
+static void cleanup_repository(void *path)
+{
+ if (g_repo)
+ git_repository_free(g_repo);
+ cl_fixture_cleanup((const char *)path);
+}
+
+void test_fetchhead_nonetwork__write(void)
+{
+ git_vector fetchhead_vector;
+ git_fetchhead_ref *fetchhead[6];
+ git_oid oid[6];
+ git_buf fetchhead_buf = GIT_BUF_INIT;
+ size_t i;
+ int equals = 0;
+
+ git_vector_init(&fetchhead_vector, 6, NULL);
+
+ cl_set_cleanup(&cleanup_repository, "./test1");
+
+ cl_git_pass(git_repository_init(&g_repo, "./test1", 0));
+
+ cl_git_pass(git_oid_fromstr(&oid[0],
+ "49322bb17d3acc9146f98c97d078513228bbf3c0"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead[0], &oid[0], 1,
+ "refs/heads/master",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[0]));
+
+ cl_git_pass(git_oid_fromstr(&oid[1],
+ "0966a434eb1a025db6b71485ab63a3bfbea520b6"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead[1], &oid[1], 0,
+ "refs/heads/first-merge",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[1]));
+
+ cl_git_pass(git_oid_fromstr(&oid[2],
+ "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead[2], &oid[2], 0,
+ "refs/heads/no-parent",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[2]));
+
+ cl_git_pass(git_oid_fromstr(&oid[3],
+ "d96c4e80345534eccee5ac7b07fc7603b56124cb"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead[3], &oid[3], 0,
+ "refs/tags/annotated_tag",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[3]));
+
+ cl_git_pass(git_oid_fromstr(&oid[4],
+ "55a1a760df4b86a02094a904dfa511deb5655905"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead[4], &oid[4], 0,
+ "refs/tags/blob",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[4]));
+
+ cl_git_pass(git_oid_fromstr(&oid[5],
+ "8f50ba15d49353813cc6e20298002c0d17b0a9ee"));
+ cl_git_pass(git_fetchhead_ref_create(&fetchhead[5], &oid[5], 0,
+ "refs/tags/commit_tree",
+ "git://github.com/libgit2/TestGitRepository"));
+ cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[5]));
+
+ git_fetchhead_write(g_repo, &fetchhead_vector);
+
+ cl_git_pass(git_futils_readbuffer(&fetchhead_buf,
+ "./test1/.git/FETCH_HEAD"));
+
+ equals = (strcmp(fetchhead_buf.ptr, FETCH_HEAD_WILDCARD_DATA) == 0);
+
+ for (i=0; i < 6; i++)
+ git_fetchhead_ref_free(fetchhead[i]);
+
+ git_buf_free(&fetchhead_buf);
+
+ git_vector_free(&fetchhead_vector);
+
+ cl_assert(equals);
+}
+
diff --git a/tests-clar/index/conflicts.c b/tests-clar/index/conflicts.c
new file mode 100644
index 000000000..e101b1659
--- /dev/null
+++ b/tests-clar/index/conflicts.c
@@ -0,0 +1,221 @@
+#include "clar_libgit2.h"
+#include "index.h"
+#include "git2/repository.h"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+#define TEST_REPO_PATH "mergedrepo"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+#define CONFLICTS_ONE_ANCESTOR_OID "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81"
+#define CONFLICTS_ONE_OUR_OID "6aea5f295304c36144ad6e9247a291b7f8112399"
+#define CONFLICTS_ONE_THEIR_OID "516bd85f78061e09ccc714561d7b504672cb52da"
+
+#define CONFLICTS_TWO_ANCESTOR_OID "84af62840be1b1c47b778a8a249f3ff45155038c"
+#define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2"
+#define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e"
+
+#define TEST_ANCESTOR_OID "f00ff00ff00ff00ff00ff00ff00ff00ff00ff00f"
+#define TEST_OUR_OID "b44bb44bb44bb44bb44bb44bb44bb44bb44bb44b"
+#define TEST_THEIR_OID "0123456789abcdef0123456789abcdef01234567"
+
+// Fixture setup and teardown
+void test_index_conflicts__initialize(void)
+{
+ repo = cl_git_sandbox_init("mergedrepo");
+ git_repository_index(&repo_index, repo);
+}
+
+void test_index_conflicts__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+void test_index_conflicts__add(void)
+{
+ git_index_entry ancestor_entry, our_entry, their_entry;
+
+ cl_assert(git_index_entrycount(repo_index) == 8);
+
+ memset(&ancestor_entry, 0x0, sizeof(git_index_entry));
+ memset(&our_entry, 0x0, sizeof(git_index_entry));
+ memset(&their_entry, 0x0, sizeof(git_index_entry));
+
+ ancestor_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID);
+
+ our_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&our_entry.oid, TEST_OUR_OID);
+
+ their_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&their_entry.oid, TEST_THEIR_OID);
+
+ cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry));
+
+ cl_assert(git_index_entrycount(repo_index) == 11);
+}
+
+void test_index_conflicts__add_fixes_incorrect_stage(void)
+{
+ git_index_entry ancestor_entry, our_entry, their_entry;
+ git_index_entry *conflict_entry[3];
+
+ cl_assert(git_index_entrycount(repo_index) == 8);
+
+ memset(&ancestor_entry, 0x0, sizeof(git_index_entry));
+ memset(&our_entry, 0x0, sizeof(git_index_entry));
+ memset(&their_entry, 0x0, sizeof(git_index_entry));
+
+ ancestor_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID);
+
+ our_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&our_entry.oid, TEST_OUR_OID);
+
+ their_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&their_entry.oid, TEST_THEIR_OID);
+
+ cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry));
+
+ cl_assert(git_index_entrycount(repo_index) == 11);
+
+ cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], repo_index, "test-one.txt"));
+
+ cl_assert(git_index_entry_stage(conflict_entry[0]) == 1);
+ cl_assert(git_index_entry_stage(conflict_entry[1]) == 2);
+ cl_assert(git_index_entry_stage(conflict_entry[2]) == 3);
+}
+
+void test_index_conflicts__get(void)
+{
+ git_index_entry *conflict_entry[3];
+ git_oid oid;
+
+ cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1],
+ &conflict_entry[2], repo_index, "conflicts-one.txt"));
+
+ cl_assert(strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[0]->oid, &oid) == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[1]->oid, &oid) == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[2]->oid, &oid) == 0);
+
+ cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1],
+ &conflict_entry[2], repo_index, "conflicts-two.txt"));
+
+ cl_assert(strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[0]->oid, &oid) == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[1]->oid, &oid) == 0);
+
+ git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID);
+ cl_assert(git_oid_cmp(&conflict_entry[2]->oid, &oid) == 0);
+}
+
+void test_index_conflicts__remove(void)
+{
+ git_index_entry *entry;
+ size_t i;
+
+ cl_assert(git_index_entrycount(repo_index) == 8);
+
+ cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-one.txt"));
+ cl_assert(git_index_entrycount(repo_index) == 5);
+
+ for (i = 0; i < git_index_entrycount(repo_index); i++) {
+ cl_assert(entry = git_index_get_byindex(repo_index, i));
+ cl_assert(strcmp(entry->path, "conflicts-one.txt") != 0);
+ }
+
+ cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-two.txt"));
+ cl_assert(git_index_entrycount(repo_index) == 2);
+
+ for (i = 0; i < git_index_entrycount(repo_index); i++) {
+ cl_assert(entry = git_index_get_byindex(repo_index, i));
+ cl_assert(strcmp(entry->path, "conflicts-two.txt") != 0);
+ }
+}
+
+void test_index_conflicts__moved_to_reuc(void)
+{
+ git_index_entry *entry;
+ size_t i;
+
+ cl_assert(git_index_entrycount(repo_index) == 8);
+
+ cl_git_mkfile("./mergedrepo/conflicts-one.txt", "new-file\n");
+
+ cl_git_pass(git_index_add_from_workdir(repo_index, "conflicts-one.txt"));
+
+ cl_assert(git_index_entrycount(repo_index) == 6);
+
+ for (i = 0; i < git_index_entrycount(repo_index); i++) {
+ cl_assert(entry = git_index_get_byindex(repo_index, i));
+
+ if (strcmp(entry->path, "conflicts-one.txt") == 0)
+ cl_assert(git_index_entry_stage(entry) == 0);
+ }
+}
+
+void test_index_conflicts__remove_all_conflicts(void)
+{
+ size_t i;
+ git_index_entry *entry;
+
+ cl_assert(git_index_entrycount(repo_index) == 8);
+
+ cl_assert_equal_i(true, git_index_has_conflicts(repo_index));
+
+ git_index_conflict_cleanup(repo_index);
+
+ cl_assert_equal_i(false, git_index_has_conflicts(repo_index));
+
+ cl_assert(git_index_entrycount(repo_index) == 2);
+
+ for (i = 0; i < git_index_entrycount(repo_index); i++) {
+ cl_assert(entry = git_index_get_byindex(repo_index, i));
+ cl_assert(git_index_entry_stage(entry) == 0);
+ }
+}
+
+void test_index_conflicts__partial(void)
+{
+ git_index_entry ancestor_entry, our_entry, their_entry;
+ git_index_entry *conflict_entry[3];
+
+ cl_assert(git_index_entrycount(repo_index) == 8);
+
+ memset(&ancestor_entry, 0x0, sizeof(git_index_entry));
+ memset(&our_entry, 0x0, sizeof(git_index_entry));
+ memset(&their_entry, 0x0, sizeof(git_index_entry));
+
+ ancestor_entry.path = "test-one.txt";
+ ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID);
+
+ cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, NULL, NULL));
+ cl_assert(git_index_entrycount(repo_index) == 9);
+
+ cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1],
+ &conflict_entry[2], repo_index, "test-one.txt"));
+
+ cl_assert(git_oid_cmp(&ancestor_entry.oid, &conflict_entry[0]->oid) == 0);
+ cl_assert(conflict_entry[1] == NULL);
+ cl_assert(conflict_entry[2] == NULL);
+}
diff --git a/tests-clar/index/filemodes.c b/tests-clar/index/filemodes.c
index 75c94e8e7..882d41748 100644
--- a/tests-clar/index/filemodes.c
+++ b/tests-clar/index/filemodes.c
@@ -25,7 +25,7 @@ void test_index_filemodes__read(void)
cl_assert_equal_i(6, git_index_entrycount(index));
for (i = 0; i < 6; ++i) {
- git_index_entry *entry = git_index_get(index, i);
+ git_index_entry *entry = git_index_get_byindex(index, i);
cl_assert(entry != NULL);
cl_assert(((entry->mode & 0100) ? 1 : 0) == expected[i]);
}
@@ -56,35 +56,15 @@ static void add_and_check_mode(
int pos;
git_index_entry *entry;
- cl_git_pass(git_index_add(index, filename, 0));
+ cl_git_pass(git_index_add_from_workdir(index, filename));
pos = git_index_find(index, filename);
cl_assert(pos >= 0);
- entry = git_index_get(index, pos);
+ entry = git_index_get_byindex(index, pos);
cl_assert(entry->mode == expect_mode);
}
-static void append_and_check_mode(
- git_index *index, const char *filename, unsigned int expect_mode)
-{
- unsigned int before, after;
- git_index_entry *entry;
-
- before = git_index_entrycount(index);
-
- cl_git_pass(git_index_append(index, filename, 0));
-
- after = git_index_entrycount(index);
- cl_assert_equal_i(before + 1, after);
-
- /* bypass git_index_get since that resorts the index */
- entry = (git_index_entry *)git_vector_get(&index->entries, after - 1);
-
- cl_assert_equal_s(entry->path, filename);
- cl_assert(expect_mode == entry->mode);
-}
-
void test_index_filemodes__untrusted(void)
{
git_config *cfg;
@@ -114,23 +94,7 @@ void test_index_filemodes__untrusted(void)
replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755);
add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
- /* 5 - append 0644 over existing 0644 -> expect 0644 */
- replace_file_with_mode("exec_off", "filemodes/exec_off.2", 0644);
- append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB);
-
- /* 6 - append 0644 over existing 0755 -> expect 0755 */
- replace_file_with_mode("exec_on", "filemodes/exec_on.2", 0644);
- append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
-
- /* 7 - append 0755 over existing 0644 -> expect 0644 */
- replace_file_with_mode("exec_off", "filemodes/exec_off.3", 0755);
- append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB);
-
- /* 8 - append 0755 over existing 0755 -> expect 0755 */
- replace_file_with_mode("exec_on", "filemodes/exec_on.3", 0755);
- append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
-
- /* 9 - add new 0644 -> expect 0644 */
+ /* 5 - add new 0644 -> expect 0644 */
cl_git_write2file("filemodes/new_off", "blah",
O_WRONLY | O_CREAT | O_TRUNC, 0644);
add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB);
@@ -139,7 +103,7 @@ void test_index_filemodes__untrusted(void)
* that doesn't support filemodes correctly, so skip it.
*/
if (can_filemode) {
- /* 10 - add 0755 -> expect 0755 */
+ /* 6 - add 0755 -> expect 0755 */
cl_git_write2file("filemodes/new_on", "blah",
O_WRONLY | O_CREAT | O_TRUNC, 0755);
add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE);
@@ -182,28 +146,12 @@ void test_index_filemodes__trusted(void)
replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755);
add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
- /* 5 - append 0644 over existing 0644 -> expect 0644 */
- replace_file_with_mode("exec_off", "filemodes/exec_off.2", 0644);
- append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB);
-
- /* 6 - append 0644 over existing 0755 -> expect 0644 */
- replace_file_with_mode("exec_on", "filemodes/exec_on.2", 0644);
- append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB);
-
- /* 7 - append 0755 over existing 0644 -> expect 0755 */
- replace_file_with_mode("exec_off", "filemodes/exec_off.3", 0755);
- append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB_EXECUTABLE);
-
- /* 8 - append 0755 over existing 0755 -> expect 0755 */
- replace_file_with_mode("exec_on", "filemodes/exec_on.3", 0755);
- append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE);
-
- /* 9 - add new 0644 -> expect 0644 */
+ /* 5 - add new 0644 -> expect 0644 */
cl_git_write2file("filemodes/new_off", "blah",
O_WRONLY | O_CREAT | O_TRUNC, 0644);
add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB);
- /* 10 - add 0755 -> expect 0755 */
+ /* 6 - add 0755 -> expect 0755 */
cl_git_write2file("filemodes/new_on", "blah",
O_WRONLY | O_CREAT | O_TRUNC, 0755);
add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE);
diff --git a/tests-clar/index/inmemory.c b/tests-clar/index/inmemory.c
new file mode 100644
index 000000000..c997b965f
--- /dev/null
+++ b/tests-clar/index/inmemory.c
@@ -0,0 +1,22 @@
+#include "clar_libgit2.h"
+
+void test_index_inmemory__can_create_an_inmemory_index(void)
+{
+ git_index *index;
+
+ cl_git_pass(git_index_new(&index));
+ cl_assert_equal_i(0, git_index_entrycount(index));
+
+ git_index_free(index);
+}
+
+void test_index_inmemory__cannot_add_from_workdir_to_an_inmemory_index(void)
+{
+ git_index *index;
+
+ cl_git_pass(git_index_new(&index));
+
+ cl_assert_equal_i(GIT_ERROR, git_index_add_from_workdir(index, "test.txt"));
+
+ git_index_free(index);
+}
diff --git a/tests-clar/index/read_tree.c b/tests-clar/index/read_tree.c
index 0479332dc..3ae883d18 100644
--- a/tests-clar/index/read_tree.c
+++ b/tests-clar/index/read_tree.c
@@ -24,19 +24,19 @@ void test_index_read_tree__read_write_involution(void)
cl_git_mkfile("./read_tree/abc/d", NULL);
cl_git_mkfile("./read_tree/abc_d", NULL);
- cl_git_pass(git_index_add(index, "abc-d", 0));
- cl_git_pass(git_index_add(index, "abc_d", 0));
- cl_git_pass(git_index_add(index, "abc/d", 0));
+ cl_git_pass(git_index_add_from_workdir(index, "abc-d"));
+ cl_git_pass(git_index_add_from_workdir(index, "abc_d"));
+ cl_git_pass(git_index_add_from_workdir(index, "abc/d"));
/* write-tree */
- cl_git_pass(git_tree_create_fromindex(&expected, index));
+ cl_git_pass(git_index_write_tree(&expected, index));
/* read-tree */
git_tree_lookup(&tree, repo, &expected);
- cl_git_pass(git_index_read_tree(index, tree, NULL));
+ cl_git_pass(git_index_read_tree(index, tree));
git_tree_free(tree);
- cl_git_pass(git_tree_create_fromindex(&tree_oid, index));
+ cl_git_pass(git_index_write_tree(&tree_oid, index));
cl_assert(git_oid_cmp(&expected, &tree_oid) == 0);
git_index_free(index);
diff --git a/tests-clar/index/rename.c b/tests-clar/index/rename.c
index eecd257fd..e16ec00c1 100644
--- a/tests-clar/index/rename.c
+++ b/tests-clar/index/rename.c
@@ -19,28 +19,28 @@ void test_index_rename__single_file(void)
cl_git_mkfile("./rename/lame.name.txt", "new_file\n");
/* This should add a new blob to the object database in 'd4/fa8600b4f37d7516bef4816ae2c64dbf029e3a' */
- cl_git_pass(git_index_add(index, "lame.name.txt", 0));
+ cl_git_pass(git_index_add_from_workdir(index, "lame.name.txt"));
cl_assert(git_index_entrycount(index) == 1);
cl_git_pass(git_oid_fromstr(&expected, "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a"));
position = git_index_find(index, "lame.name.txt");
- entry = git_index_get(index, position);
+ entry = git_index_get_byindex(index, position);
cl_assert(git_oid_cmp(&expected, &entry->oid) == 0);
/* This removes the entry from the index, but not from the object database */
- cl_git_pass(git_index_remove(index, position));
+ cl_git_pass(git_index_remove(index, "lame.name.txt", 0));
cl_assert(git_index_entrycount(index) == 0);
p_rename("./rename/lame.name.txt", "./rename/fancy.name.txt");
- cl_git_pass(git_index_add(index, "fancy.name.txt", 0));
+ cl_git_pass(git_index_add_from_workdir(index, "fancy.name.txt"));
cl_assert(git_index_entrycount(index) == 1);
position = git_index_find(index, "fancy.name.txt");
- entry = git_index_get(index, position);
+ entry = git_index_get_byindex(index, position);
cl_assert(git_oid_cmp(&expected, &entry->oid) == 0);
git_index_free(index);
diff --git a/tests-clar/index/reuc.c b/tests-clar/index/reuc.c
new file mode 100644
index 000000000..c7c394444
--- /dev/null
+++ b/tests-clar/index/reuc.c
@@ -0,0 +1,236 @@
+#include "clar_libgit2.h"
+#include "index.h"
+#include "git2/repository.h"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+#define TEST_REPO_PATH "mergedrepo"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+#define ONE_ANCESTOR_OID "478871385b9cd03908c5383acfd568bef023c6b3"
+#define ONE_OUR_OID "4458b8bc9e72b6c8755ae456f60e9844d0538d8c"
+#define ONE_THEIR_OID "8b72416545c7e761b64cecad4f1686eae4078aa8"
+
+#define TWO_ANCESTOR_OID "9d81f82fccc7dcd7de7a1ffead1815294c2e092c"
+#define TWO_OUR_OID "8f3c06cff9a83757cec40c80bc9bf31a2582bde9"
+#define TWO_THEIR_OID "887b153b165d32409c70163e0f734c090f12f673"
+
+// Fixture setup and teardown
+void test_index_reuc__initialize(void)
+{
+ repo = cl_git_sandbox_init("mergedrepo");
+ git_repository_index(&repo_index, repo);
+}
+
+void test_index_reuc__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+void test_index_reuc__read_bypath(void)
+{
+ const git_index_reuc_entry *reuc;
+ git_oid oid;
+
+ cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index));
+
+ cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "two.txt"));
+
+ cl_assert(strcmp(reuc->path, "two.txt") == 0);
+ cl_assert(reuc->mode[0] == 0100644);
+ cl_assert(reuc->mode[1] == 0100644);
+ cl_assert(reuc->mode[2] == 0100644);
+ git_oid_fromstr(&oid, TWO_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_OUR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_THEIR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0);
+
+ cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "one.txt"));
+
+ cl_assert(strcmp(reuc->path, "one.txt") == 0);
+ cl_assert(reuc->mode[0] == 0100644);
+ cl_assert(reuc->mode[1] == 0100644);
+ cl_assert(reuc->mode[2] == 0100644);
+ git_oid_fromstr(&oid, ONE_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0);
+ git_oid_fromstr(&oid, ONE_OUR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0);
+ git_oid_fromstr(&oid, ONE_THEIR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0);
+}
+
+void test_index_reuc__ignore_case(void)
+{
+ const git_index_reuc_entry *reuc;
+ git_oid oid;
+ int index_caps;
+
+ index_caps = git_index_caps(repo_index);
+
+ index_caps &= ~GIT_INDEXCAP_IGNORE_CASE;
+ cl_git_pass(git_index_set_caps(repo_index, index_caps));
+
+ cl_assert(!git_index_reuc_get_bypath(repo_index, "TWO.txt"));
+
+ index_caps |= GIT_INDEXCAP_IGNORE_CASE;
+ cl_git_pass(git_index_set_caps(repo_index, index_caps));
+
+ cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index));
+
+ cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "TWO.txt"));
+
+ cl_assert(strcmp(reuc->path, "two.txt") == 0);
+ cl_assert(reuc->mode[0] == 0100644);
+ cl_assert(reuc->mode[1] == 0100644);
+ cl_assert(reuc->mode[2] == 0100644);
+ git_oid_fromstr(&oid, TWO_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_OUR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_THEIR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0);
+}
+
+void test_index_reuc__read_byindex(void)
+{
+ const git_index_reuc_entry *reuc;
+ git_oid oid;
+
+ cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index));
+
+ cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0));
+
+ cl_assert(strcmp(reuc->path, "one.txt") == 0);
+ cl_assert(reuc->mode[0] == 0100644);
+ cl_assert(reuc->mode[1] == 0100644);
+ cl_assert(reuc->mode[2] == 0100644);
+ git_oid_fromstr(&oid, ONE_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0);
+ git_oid_fromstr(&oid, ONE_OUR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0);
+ git_oid_fromstr(&oid, ONE_THEIR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0);
+
+ cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1));
+
+ cl_assert(strcmp(reuc->path, "two.txt") == 0);
+ cl_assert(reuc->mode[0] == 0100644);
+ cl_assert(reuc->mode[1] == 0100644);
+ cl_assert(reuc->mode[2] == 0100644);
+ git_oid_fromstr(&oid, TWO_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_OUR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_THEIR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0);
+}
+
+void test_index_reuc__updates_existing(void)
+{
+ const git_index_reuc_entry *reuc;
+ git_oid ancestor_oid, our_oid, their_oid, oid;
+ int index_caps;
+
+ git_index_clear(repo_index);
+
+ index_caps = git_index_caps(repo_index);
+
+ index_caps |= GIT_INDEXCAP_IGNORE_CASE;
+ cl_git_pass(git_index_set_caps(repo_index, index_caps));
+
+ git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID);
+ git_oid_fromstr(&our_oid, TWO_OUR_OID);
+ git_oid_fromstr(&their_oid, TWO_THEIR_OID);
+
+ cl_git_pass(git_index_reuc_add(repo_index, "two.txt",
+ 0100644, &ancestor_oid,
+ 0100644, &our_oid,
+ 0100644, &their_oid));
+
+ cl_git_pass(git_index_reuc_add(repo_index, "TWO.txt",
+ 0100644, &our_oid,
+ 0100644, &their_oid,
+ 0100644, &ancestor_oid));
+
+ cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index));
+
+ cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0));
+
+ cl_assert(strcmp(reuc->path, "TWO.txt") == 0);
+ git_oid_fromstr(&oid, TWO_OUR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_THEIR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0);
+}
+
+void test_index_reuc__remove(void)
+{
+ git_oid oid;
+ const git_index_reuc_entry *reuc;
+
+ cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index));
+
+ cl_git_pass(git_index_reuc_remove(repo_index, 0));
+ cl_git_fail(git_index_reuc_remove(repo_index, 1));
+
+ cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index));
+
+ cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0));
+
+ cl_assert(strcmp(reuc->path, "two.txt") == 0);
+ cl_assert(reuc->mode[0] == 0100644);
+ cl_assert(reuc->mode[1] == 0100644);
+ cl_assert(reuc->mode[2] == 0100644);
+ git_oid_fromstr(&oid, TWO_ANCESTOR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_OUR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0);
+ git_oid_fromstr(&oid, TWO_THEIR_OID);
+ cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0);
+}
+
+void test_index_reuc__write(void)
+{
+ git_oid ancestor_oid, our_oid, their_oid;
+ const git_index_reuc_entry *reuc;
+
+ git_index_clear(repo_index);
+
+ /* Write out of order to ensure sorting is correct */
+ git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID);
+ git_oid_fromstr(&our_oid, TWO_OUR_OID);
+ git_oid_fromstr(&their_oid, TWO_THEIR_OID);
+
+ cl_git_pass(git_index_reuc_add(repo_index, "two.txt",
+ 0100644, &ancestor_oid,
+ 0100644, &our_oid,
+ 0100644, &their_oid));
+
+ git_oid_fromstr(&ancestor_oid, ONE_ANCESTOR_OID);
+ git_oid_fromstr(&our_oid, ONE_OUR_OID);
+ git_oid_fromstr(&their_oid, ONE_THEIR_OID);
+
+ cl_git_pass(git_index_reuc_add(repo_index, "one.txt",
+ 0100644, &ancestor_oid,
+ 0100644, &our_oid,
+ 0100644, &their_oid));
+
+ cl_git_pass(git_index_write(repo_index));
+
+ cl_git_pass(git_index_read(repo_index));
+ cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index));
+
+ /* ensure sort order was round-tripped correct */
+ cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0));
+ cl_assert(strcmp(reuc->path, "one.txt") == 0);
+
+ cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1));
+ cl_assert(strcmp(reuc->path, "two.txt") == 0);
+}
+
diff --git a/tests-clar/index/stage.c b/tests-clar/index/stage.c
new file mode 100644
index 000000000..ba6f1b806
--- /dev/null
+++ b/tests-clar/index/stage.c
@@ -0,0 +1,60 @@
+#include "clar_libgit2.h"
+#include "index.h"
+#include "git2/repository.h"
+
+static git_repository *repo;
+static git_index *repo_index;
+
+#define TEST_REPO_PATH "mergedrepo"
+#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index"
+
+// Fixture setup and teardown
+void test_index_stage__initialize(void)
+{
+ repo = cl_git_sandbox_init("mergedrepo");
+ git_repository_index(&repo_index, repo);
+}
+
+void test_index_stage__cleanup(void)
+{
+ git_index_free(repo_index);
+ cl_git_sandbox_cleanup();
+}
+
+
+void test_index_stage__add_always_adds_stage_0(void)
+{
+ int entry_idx;
+ git_index_entry *entry;
+
+ cl_git_mkfile("./mergedrepo/new-file.txt", "new-file\n");
+
+ cl_git_pass(git_index_add_from_workdir(repo_index, "new-file.txt"));
+
+ cl_assert((entry_idx = git_index_find(repo_index, "new-file.txt")) >= 0);
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 0);
+}
+
+void test_index_stage__find_gets_first_stage(void)
+{
+ int entry_idx;
+ git_index_entry *entry;
+
+ cl_assert((entry_idx = git_index_find(repo_index, "one.txt")) >= 0);
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 0);
+
+ cl_assert((entry_idx = git_index_find(repo_index, "two.txt")) >= 0);
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 0);
+
+ cl_assert((entry_idx = git_index_find(repo_index, "conflicts-one.txt")) >= 0);
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 1);
+
+ cl_assert((entry_idx = git_index_find(repo_index, "conflicts-two.txt")) >= 0);
+ cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL);
+ cl_assert(git_index_entry_stage(entry) == 1);
+}
+
diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c
index a535d6815..3b71b704d 100644
--- a/tests-clar/index/tests.c
+++ b/tests-clar/index/tests.c
@@ -231,16 +231,60 @@ void test_index_tests__add(void)
cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"));
/* Add the new file to the index */
- cl_git_pass(git_index_add(index, "test.txt", 0));
+ cl_git_pass(git_index_add_from_workdir(index, "test.txt"));
/* Wow... it worked! */
cl_assert(git_index_entrycount(index) == 1);
- entry = git_index_get(index, 0);
+ entry = git_index_get_byindex(index, 0);
/* And the built-in hashing mechanism worked as expected */
cl_assert(git_oid_cmp(&id1, &entry->oid) == 0);
+ /* Test access by path instead of index */
+ cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL);
+ cl_assert(git_oid_cmp(&id1, &entry->oid) == 0);
+
git_index_free(index);
git_repository_free(repo);
}
+void test_index_tests__add_from_workdir_to_a_bare_repository_returns_EBAREPO(void)
+{
+ git_repository *bare_repo;
+ git_index *index;
+
+ cl_git_pass(git_repository_open(&bare_repo, cl_fixture("testrepo.git")));
+ cl_git_pass(git_repository_index(&index, bare_repo));
+
+ cl_assert_equal_i(GIT_EBAREREPO, git_index_add_from_workdir(index, "test.txt"));
+
+ git_index_free(index);
+ git_repository_free(bare_repo);
+}
+
+/* Test that writing an invalid filename fails */
+void test_index_tests__write_invalid_filename(void)
+{
+ git_repository *repo;
+ git_index *index;
+ git_oid expected;
+
+ p_mkdir("read_tree", 0700);
+
+ cl_git_pass(git_repository_init(&repo, "./read_tree", 0));
+ cl_git_pass(git_repository_index(&index, repo));
+
+ cl_assert(git_index_entrycount(index) == 0);
+
+ cl_git_mkfile("./read_tree/.git/hello", NULL);
+
+ cl_git_pass(git_index_add_from_workdir(index, ".git/hello"));
+
+ /* write-tree */
+ cl_git_fail(git_index_write_tree(&expected, index));
+
+ git_index_free(index);
+ git_repository_free(repo);
+
+ cl_fixture_cleanup("read_tree");
+}
diff --git a/tests-clar/network/fetch.c b/tests-clar/network/fetch.c
index 5ff7b0af8..d2140b5f4 100644
--- a/tests-clar/network/fetch.c
+++ b/tests-clar/network/fetch.c
@@ -18,22 +18,24 @@ void test_network_fetch__cleanup(void)
static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data)
{
- refname = refname;
- a = a;
- b = b;
- data = data;
+ GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); GIT_UNUSED(data);
++counter;
return 0;
}
+static void progress(const git_transfer_progress *stats, void *payload)
+{
+ int *bytes_received = (int*)payload;
+ *bytes_received = stats->received_bytes;
+}
+
static void do_fetch(const char *url, int flag, int n)
{
git_remote *remote;
- git_off_t bytes;
- git_indexer_stats stats;
git_remote_callbacks callbacks;
+ int bytes_received = 0;
memset(&callbacks, 0, sizeof(git_remote_callbacks));
callbacks.update_tips = update_tips;
@@ -43,10 +45,11 @@ static void do_fetch(const char *url, int flag, int n)
git_remote_set_callbacks(remote, &callbacks);
git_remote_set_autotag(remote, flag);
cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH));
- cl_git_pass(git_remote_download(remote, &bytes, &stats));
+ cl_git_pass(git_remote_download(remote, progress, &bytes_received));
git_remote_disconnect(remote);
cl_git_pass(git_remote_update_tips(remote));
cl_assert_equal_i(counter, n);
+ cl_assert(bytes_received > 0);
git_remote_free(remote);
}
diff --git a/tests-clar/network/fetchlocal.c b/tests-clar/network/fetchlocal.c
new file mode 100644
index 000000000..bff0bb06b
--- /dev/null
+++ b/tests-clar/network/fetchlocal.c
@@ -0,0 +1,65 @@
+#include "clar_libgit2.h"
+
+#include "buffer.h"
+#include "path.h"
+#include "remote.h"
+
+static void transfer_cb(const git_transfer_progress *stats, void *payload)
+{
+ int *callcount = (int*)payload;
+ GIT_UNUSED(stats);
+ (*callcount)++;
+}
+
+void test_network_fetchlocal__complete(void)
+{
+ git_repository *repo;
+ git_remote *origin;
+ int callcount = 0;
+ git_strarray refnames = {0};
+
+ const char *url = cl_git_fixture_url("testrepo.git");
+ cl_git_pass(git_repository_init(&repo, "foo", true));
+
+ cl_git_pass(git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, url));
+ cl_git_pass(git_remote_connect(origin, GIT_DIR_FETCH));
+ cl_git_pass(git_remote_download(origin, transfer_cb, &callcount));
+ cl_git_pass(git_remote_update_tips(origin));
+
+ cl_git_pass(git_reference_list(&refnames, repo, GIT_REF_LISTALL));
+ cl_assert_equal_i(18, refnames.count);
+ cl_assert(callcount > 0);
+
+ git_strarray_free(&refnames);
+ git_remote_free(origin);
+ git_repository_free(repo);
+}
+
+void test_network_fetchlocal__partial(void)
+{
+ git_repository *repo = cl_git_sandbox_init("partial-testrepo");
+ git_remote *origin;
+ int callcount = 0;
+ git_strarray refnames = {0};
+ const char *url;
+
+ cl_git_pass(git_reference_list(&refnames, repo, GIT_REF_LISTALL));
+ cl_assert_equal_i(1, refnames.count);
+
+ url = cl_git_fixture_url("testrepo.git");
+ cl_git_pass(git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, url));
+ cl_git_pass(git_remote_connect(origin, GIT_DIR_FETCH));
+ cl_git_pass(git_remote_download(origin, transfer_cb, &callcount));
+ cl_git_pass(git_remote_update_tips(origin));
+
+ git_strarray_free(&refnames);
+
+ cl_git_pass(git_reference_list(&refnames, repo, GIT_REF_LISTALL));
+ cl_assert_equal_i(19, refnames.count); /* 18 remote + 1 local */
+ cl_assert(callcount > 0);
+
+ git_strarray_free(&refnames);
+ git_remote_free(origin);
+
+ cl_git_sandbox_cleanup();
+}
diff --git a/tests-clar/network/remotelocal.c b/tests-clar/network/remotelocal.c
index 3ff619748..f7ae83423 100644
--- a/tests-clar/network/remotelocal.c
+++ b/tests-clar/network/remotelocal.c
@@ -1,5 +1,4 @@
#include "clar_libgit2.h"
-#include "transport.h"
#include "buffer.h"
#include "path.h"
#include "posix.h"
@@ -8,45 +7,6 @@ static git_repository *repo;
static git_buf file_path_buf = GIT_BUF_INIT;
static git_remote *remote;
-static void build_local_file_url(git_buf *out, const char *fixture)
-{
- const char *in_buf;
-
- git_buf path_buf = GIT_BUF_INIT;
-
- cl_git_pass(git_path_prettify_dir(&path_buf, fixture, NULL));
- cl_git_pass(git_buf_puts(out, "file://"));
-
-#ifdef _MSC_VER
- /*
- * A FILE uri matches the following format: file://[host]/path
- * where "host" can be empty and "path" is an absolute path to the resource.
- *
- * In this test, no hostname is used, but we have to ensure the leading triple slashes:
- *
- * *nix: file:///usr/home/...
- * Windows: file:///C:/Users/...
- */
- cl_git_pass(git_buf_putc(out, '/'));
-#endif
-
- in_buf = git_buf_cstr(&path_buf);
-
- /*
- * A very hacky Url encoding that only takes care of escaping the spaces
- */
- while (*in_buf) {
- if (*in_buf == ' ')
- cl_git_pass(git_buf_puts(out, "%20"));
- else
- cl_git_pass(git_buf_putc(out, *in_buf));
-
- in_buf++;
- }
-
- git_buf_free(&path_buf);
-}
-
void test_network_remotelocal__initialize(void)
{
cl_git_pass(git_repository_init(&repo, "remotelocal/", 0));
@@ -83,7 +43,7 @@ static int ensure_peeled__cb(git_remote_head *head, void *payload)
static void connect_to_local_repository(const char *local_repository)
{
- build_local_file_url(&file_path_buf, local_repository);
+ git_buf_sets(&file_path_buf, cl_git_path_url(local_repository));
cl_git_pass(git_remote_new(&remote, repo, NULL, git_buf_cstr(&file_path_buf), NULL));
cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH));
diff --git a/tests-clar/network/remoterename.c b/tests-clar/network/remoterename.c
new file mode 100644
index 000000000..70041f45d
--- /dev/null
+++ b/tests-clar/network/remoterename.c
@@ -0,0 +1,201 @@
+#include "clar_libgit2.h"
+#include "config/config_helpers.h"
+
+#include "repository.h"
+
+static git_remote *_remote;
+static git_repository *_repo;
+
+void test_network_remoterename__initialize(void)
+{
+ _repo = cl_git_sandbox_init("testrepo.git");
+
+ cl_git_pass(git_remote_load(&_remote, _repo, "test"));
+}
+
+void test_network_remoterename__cleanup(void)
+{
+ git_remote_free(_remote);
+
+ cl_git_sandbox_cleanup();
+}
+
+static int dont_call_me_cb(const char *fetch_refspec, void *payload)
+{
+ GIT_UNUSED(fetch_refspec);
+ GIT_UNUSED(payload);
+
+ cl_assert(false);
+
+ return -1;
+}
+
+void test_network_remoterename__renaming_a_remote_moves_related_configuration_section(void)
+{
+ assert_config_entry_existence(_repo, "remote.test.fetch", true);
+ assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false);
+
+ cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL));
+
+ assert_config_entry_existence(_repo, "remote.test.fetch", false);
+ assert_config_entry_existence(_repo, "remote.just/renamed.fetch", true);
+}
+
+void test_network_remoterename__renaming_a_remote_updates_branch_related_configuration_entries(void)
+{
+ assert_config_entry_value(_repo, "branch.master.remote", "test");
+
+ cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL));
+
+ assert_config_entry_value(_repo, "branch.master.remote", "just/renamed");
+}
+
+void test_network_remoterename__renaming_a_remote_updates_default_fetchrefspec(void)
+{
+ cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL));
+
+ assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/heads/*:refs/remotes/just/renamed/*");
+}
+
+void test_network_remoterename__renaming_a_remote_without_a_fetchrefspec_doesnt_create_one(void)
+{
+ git_config *config;
+
+ git_remote_free(_remote);
+ cl_git_pass(git_repository_config__weakptr(&config, _repo));
+ cl_git_pass(git_config_delete(config, "remote.test.fetch"));
+
+ cl_git_pass(git_remote_load(&_remote, _repo, "test"));
+
+ assert_config_entry_existence(_repo, "remote.test.fetch", false);
+
+ cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL));
+
+ assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false);
+}
+
+static int ensure_refspecs(const char* refspec_name, void *payload)
+{
+ int i = 0;
+ bool found = false;
+ const char ** exp = (const char **)payload;
+
+ while (exp[i]) {
+ if (strcmp(exp[i++], refspec_name))
+ continue;
+
+ found = true;
+ break;
+ }
+
+ cl_assert(found);
+
+ return 0;
+}
+
+void test_network_remoterename__renaming_a_remote_notifies_of_non_default_fetchrefspec(void)
+{
+ git_config *config;
+
+ char *expected_refspecs[] = {
+ "+refs/*:refs/*",
+ NULL
+ };
+
+ git_remote_free(_remote);
+ cl_git_pass(git_repository_config__weakptr(&config, _repo));
+ cl_git_pass(git_config_set_string(config, "remote.test.fetch", "+refs/*:refs/*"));
+ cl_git_pass(git_remote_load(&_remote, _repo, "test"));
+
+ cl_git_pass(git_remote_rename(_remote, "just/renamed", ensure_refspecs, &expected_refspecs));
+
+ assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/*:refs/*");
+}
+
+void test_network_remoterename__new_name_can_contain_dots(void)
+{
+ cl_git_pass(git_remote_rename(_remote, "just.renamed", dont_call_me_cb, NULL));
+ cl_assert_equal_s("just.renamed", git_remote_name(_remote));
+}
+
+void test_network_remoterename__new_name_must_conform_to_reference_naming_conventions(void)
+{
+ cl_git_fail(git_remote_rename(_remote, "new@{name", dont_call_me_cb, NULL));
+}
+
+void test_network_remoterename__renamed_name_is_persisted(void)
+{
+ git_remote *renamed;
+ git_repository *another_repo;
+
+ cl_git_fail(git_remote_load(&renamed, _repo, "just/renamed"));
+
+ cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL));
+
+ cl_git_pass(git_repository_open(&another_repo, "testrepo.git"));
+ cl_git_pass(git_remote_load(&renamed, _repo, "just/renamed"));
+
+ git_remote_free(renamed);
+ git_repository_free(another_repo);
+}
+
+void test_network_remoterename__cannot_overwrite_an_existing_remote(void)
+{
+ cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(_remote, "test", dont_call_me_cb, NULL));
+ cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(_remote, "test_with_pushurl", dont_call_me_cb, NULL));
+}
+
+void test_network_remoterename__renaming_an_inmemory_remote_persists_it(void)
+{
+ git_remote *remote;
+
+ assert_config_entry_existence(_repo, "remote.durable.url", false);
+
+ cl_git_pass(git_remote_new(&remote, _repo, NULL, "git://github.com/libgit2/durable.git", NULL));
+
+ assert_config_entry_existence(_repo, "remote.durable.url", false);
+
+ cl_git_pass(git_remote_rename(remote, "durable", dont_call_me_cb, NULL));
+
+ assert_config_entry_value(_repo, "remote.durable.url", "git://github.com/libgit2/durable.git");
+
+ git_remote_free(remote);
+}
+
+void test_network_remoterename__renaming_an_inmemory_nameless_remote_notifies_the_inability_to_update_the_fetch_refspec(void)
+{
+ git_remote *remote;
+
+ char *expected_refspecs[] = {
+ "+refs/heads/*:refs/remotes/volatile/*",
+ NULL
+ };
+
+ assert_config_entry_existence(_repo, "remote.volatile.url", false);
+
+ cl_git_pass(git_remote_new(
+ &remote,
+ _repo,
+ NULL,
+ "git://github.com/libgit2/volatile.git",
+ "+refs/heads/*:refs/remotes/volatile/*"));
+
+ cl_git_pass(git_remote_rename(remote, "durable", ensure_refspecs, &expected_refspecs));
+
+ git_remote_free(remote);
+}
+
+void test_network_remoterename__renaming_a_remote_moves_the_underlying_reference(void)
+{
+ git_reference *underlying;
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed"));
+ cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/test/master"));
+ git_reference_free(underlying);
+
+ cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/test/master"));
+ cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed/master"));
+ git_reference_free(underlying);
+}
diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c
index 91c3e879d..1d58aba75 100644
--- a/tests-clar/network/remotes.c
+++ b/tests-clar/network/remotes.c
@@ -1,7 +1,6 @@
#include "clar_libgit2.h"
#include "buffer.h"
#include "refspec.h"
-#include "transport.h"
#include "remote.h"
static git_remote *_remote;
@@ -10,9 +9,8 @@ static const git_refspec *_refspec;
void test_network_remotes__initialize(void)
{
- cl_fixture_sandbox("testrepo.git");
+ _repo = cl_git_sandbox_init("testrepo.git");
- cl_git_pass(git_repository_open(&_repo, "testrepo.git"));
cl_git_pass(git_remote_load(&_remote, _repo, "test"));
_refspec = git_remote_fetchspec(_remote);
@@ -22,8 +20,7 @@ void test_network_remotes__initialize(void)
void test_network_remotes__cleanup(void)
{
git_remote_free(_remote);
- git_repository_free(_repo);
- cl_fixture_cleanup("testrepo.git");
+ cl_git_sandbox_cleanup();
}
void test_network_remotes__parsing(void)
@@ -73,7 +70,7 @@ void test_network_remotes__parsing_local_path_fails_if_path_not_found(void)
void test_network_remotes__supported_transport_methods_are_supported(void)
{
- cl_assert( git_remote_supported_url("git://github.com/libgit2/libgit2") );
+ cl_assert( git_remote_supported_url("git://github.com/libgit2/libgit2") );
}
void test_network_remotes__unsupported_transport_methods_are_unsupported(void)
@@ -186,13 +183,13 @@ void test_network_remotes__list(void)
git_config *cfg;
cl_git_pass(git_remote_list(&list, _repo));
- cl_assert(list.count == 3);
+ cl_assert(list.count == 4);
git_strarray_free(&list);
cl_git_pass(git_repository_config(&cfg, _repo));
cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com"));
cl_git_pass(git_remote_list(&list, _repo));
- cl_assert(list.count == 4);
+ cl_assert(list.count == 5);
git_strarray_free(&list);
git_config_free(cfg);
@@ -226,6 +223,29 @@ void test_network_remotes__add(void)
cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2");
}
+void test_network_remotes__cannot_add_a_nameless_remote(void)
+{
+ git_remote *remote;
+
+ cl_git_fail(git_remote_add(&remote, _repo, NULL, "git://github.com/libgit2/libgit2"));
+ cl_git_fail(git_remote_add(&remote, _repo, "", "git://github.com/libgit2/libgit2"));
+}
+
+void test_network_remotes__cannot_save_a_nameless_remote(void)
+{
+ git_remote *remote;
+
+ cl_git_pass(git_remote_new(&remote, _repo, NULL, "git://github.com/libgit2/libgit2", NULL));
+
+ cl_git_fail(git_remote_save(remote));
+ git_remote_free(remote);
+
+ cl_git_pass(git_remote_new(&remote, _repo, "", "git://github.com/libgit2/libgit2", NULL));
+
+ cl_git_fail(git_remote_save(remote));
+ git_remote_free(remote);
+}
+
void test_network_remotes__tagopt(void)
{
const char *opt;
@@ -249,3 +269,11 @@ void test_network_remotes__tagopt(void)
git_config_free(cfg);
}
+
+void test_network_remotes__cannot_load_with_an_empty_url(void)
+{
+ git_remote *remote;
+
+ cl_git_fail(git_remote_load(&remote, _repo, "empty-remote-url"));
+ cl_assert(giterr_last()->klass == GITERR_INVALID);
+}
diff --git a/tests-clar/object/blob/write.c b/tests-clar/object/blob/write.c
index 87a9e2072..6d4cbab4f 100644
--- a/tests-clar/object/blob/write.c
+++ b/tests-clar/object/blob/write.c
@@ -49,7 +49,7 @@ void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolut
assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk);
git_buf_free(&full_path);
- cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES));
}
void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_filepath(void)
@@ -65,5 +65,5 @@ void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_fi
assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk);
git_buf_free(&full_path);
- cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES));
}
diff --git a/tests-clar/object/commit/commitstagedfile.c b/tests-clar/object/commit/commitstagedfile.c
index 882fb49ae..eb78cedaa 100644
--- a/tests-clar/object/commit/commitstagedfile.c
+++ b/tests-clar/object/commit/commitstagedfile.c
@@ -71,9 +71,9 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void)
*/
cl_git_mkfile("treebuilder/test.txt", "test\n");
cl_git_pass(git_repository_index(&index, repo));
- cl_git_pass(git_index_add(index, "test.txt", 0));
+ cl_git_pass(git_index_add_from_workdir(index, "test.txt"));
- entry = git_index_get(index, 0);
+ entry = git_index_get_byindex(index, 0);
cl_assert(git_oid_cmp(&expected_blob_oid, &entry->oid) == 0);
@@ -99,7 +99,7 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void)
/*
* Build the tree from the index
*/
- cl_git_pass(git_tree_create_fromindex(&tree_oid, index));
+ cl_git_pass(git_index_write_tree(&tree_oid, index));
cl_assert(git_oid_cmp(&expected_tree_oid, &tree_oid) == 0);
@@ -128,68 +128,3 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void)
git_tree_free(tree);
git_index_free(index);
}
-
-void test_object_commit_commitstagedfile__message_prettify(void)
-{
- char buffer[100];
-
- cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 0) == 1);
- cl_assert_equal_s(buffer, "");
- cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 1) == 1);
- cl_assert_equal_s(buffer, "");
-
- cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 0));
- cl_assert_equal_s("Short\n", buffer);
- cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 1));
- cl_assert_equal_s("Short\n", buffer);
-
- cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 0) > 0);
- cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n# with some comments still in\n");
-
- cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 1) > 0);
- cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n");
-
- /* try out overflow */
- cl_assert(git_message_prettify(buffer, sizeof(buffer),
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
- "1234567890" "1234567890" "1234567890" "1234567890" "12345678",
- 0) > 0);
- cl_assert_equal_s(buffer,
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
- "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n");
-
- cl_assert(git_message_prettify(buffer, sizeof(buffer),
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
- "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n",
- 0) > 0);
- cl_assert_equal_s(buffer,
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
- "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n");
-
- cl_git_fail(git_message_prettify(buffer, sizeof(buffer),
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
- "1234567890" "1234567890" "1234567890" "1234567890" "123456789",
- 0));
- cl_git_fail(git_message_prettify(buffer, sizeof(buffer),
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
- "1234567890" "1234567890" "1234567890" "1234567890" "123456789\n",
- 0));
- cl_git_fail(git_message_prettify(buffer, sizeof(buffer),
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890",
- 0));
- cl_git_fail(git_message_prettify(buffer, sizeof(buffer),
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890""x",
- 0));
-
- cl_assert(git_message_prettify(buffer, sizeof(buffer),
- "1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n"
- "# 1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n"
- "1234567890",
- 1) > 0);
-
- cl_assert(git_message_prettify(NULL, 0, "", 0) == 1);
- cl_assert(git_message_prettify(NULL, 0, "Short test", 0) == 12);
- cl_assert(git_message_prettify(NULL, 0, "Test\n# with\nComments", 1) == 15);
-}
diff --git a/tests-clar/object/message.c b/tests-clar/object/message.c
index 43be8b152..7ef6374b3 100644
--- a/tests-clar/object/message.c
+++ b/tests-clar/object/message.c
@@ -169,3 +169,68 @@ void test_object_message__keep_comments(void)
assert_message_prettifying("# comment\n" ttt "\n", "# comment\n" ttt "\n", 0);
assert_message_prettifying(ttt "\n" "# comment\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 0);
}
+
+void test_object_message__message_prettify(void)
+{
+ char buffer[100];
+
+ cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 0) == 1);
+ cl_assert_equal_s(buffer, "");
+ cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 1) == 1);
+ cl_assert_equal_s(buffer, "");
+
+ cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 0));
+ cl_assert_equal_s("Short\n", buffer);
+ cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 1));
+ cl_assert_equal_s("Short\n", buffer);
+
+ cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 0) > 0);
+ cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n# with some comments still in\n");
+
+ cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 1) > 0);
+ cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n");
+
+ /* try out overflow */
+ cl_assert(git_message_prettify(buffer, sizeof(buffer),
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "1234567890" "12345678",
+ 0) > 0);
+ cl_assert_equal_s(buffer,
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n");
+
+ cl_assert(git_message_prettify(buffer, sizeof(buffer),
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n",
+ 0) > 0);
+ cl_assert_equal_s(buffer,
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n");
+
+ cl_git_fail(git_message_prettify(buffer, sizeof(buffer),
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "1234567890" "123456789",
+ 0));
+ cl_git_fail(git_message_prettify(buffer, sizeof(buffer),
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "1234567890" "123456789\n",
+ 0));
+ cl_git_fail(git_message_prettify(buffer, sizeof(buffer),
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890",
+ 0));
+ cl_git_fail(git_message_prettify(buffer, sizeof(buffer),
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890"
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890""x",
+ 0));
+
+ cl_assert(git_message_prettify(buffer, sizeof(buffer),
+ "1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n"
+ "# 1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n"
+ "1234567890",
+ 1) > 0);
+
+ cl_assert(git_message_prettify(NULL, 0, "", 0) == 1);
+ cl_assert(git_message_prettify(NULL, 0, "Short test", 0) == 12);
+ cl_assert(git_message_prettify(NULL, 0, "Test\n# with\nComments", 1) == 15);
+}
diff --git a/tests-clar/object/raw/hash.c b/tests-clar/object/raw/hash.c
index 4b8b1b74c..f26035e45 100644
--- a/tests-clar/object/raw/hash.c
+++ b/tests-clar/object/raw/hash.c
@@ -23,25 +23,25 @@ static char *bye_text = "bye world\n";
void test_object_raw_hash__hash_by_blocks(void)
{
- git_hash_ctx *ctx;
+ git_hash_ctx ctx;
git_oid id1, id2;
- cl_assert((ctx = git_hash_new_ctx()) != NULL);
+ cl_git_pass(git_hash_ctx_init(&ctx));
/* should already be init'd */
- git_hash_update(ctx, hello_text, strlen(hello_text));
- git_hash_final(&id2, ctx);
+ cl_git_pass(git_hash_update(&ctx, hello_text, strlen(hello_text)));
+ cl_git_pass(git_hash_final(&id2, &ctx));
cl_git_pass(git_oid_fromstr(&id1, hello_id));
cl_assert(git_oid_cmp(&id1, &id2) == 0);
/* reinit should permit reuse */
- git_hash_init(ctx);
- git_hash_update(ctx, bye_text, strlen(bye_text));
- git_hash_final(&id2, ctx);
+ cl_git_pass(git_hash_init(&ctx));
+ cl_git_pass(git_hash_update(&ctx, bye_text, strlen(bye_text)));
+ cl_git_pass(git_hash_final(&id2, &ctx));
cl_git_pass(git_oid_fromstr(&id1, bye_id));
cl_assert(git_oid_cmp(&id1, &id2) == 0);
- git_hash_free_ctx(ctx);
+ git_hash_ctx_cleanup(&ctx);
}
void test_object_raw_hash__hash_buffer_in_single_call(void)
diff --git a/tests-clar/object/raw/short.c b/tests-clar/object/raw/short.c
index 14b1ae219..93c79b6a5 100644
--- a/tests-clar/object/raw/short.c
+++ b/tests-clar/object/raw/short.c
@@ -43,7 +43,7 @@ void test_object_raw_short__oid_shortener_stresstest_git_oid_shorten(void)
for (i = 0; i < MAX_OIDS; ++i) {
char *oid_text;
- sprintf(number_buffer, "%u", (unsigned int)i);
+ p_snprintf(number_buffer, 16, "%u", (unsigned int)i);
git_hash_buf(&oid, number_buffer, strlen(number_buffer));
oid_text = git__malloc(GIT_OID_HEXSZ + 1);
diff --git a/tests-clar/object/tag/read.c b/tests-clar/object/tag/read.c
index 4dd5cc253..53272e91c 100644
--- a/tests-clar/object/tag/read.c
+++ b/tests-clar/object/tag/read.c
@@ -7,6 +7,8 @@ static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980";
static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d";
static const char *bad_tag_id = "eda9f45a2a98d4c17a09d681d88569fa4ea91755";
static const char *badly_tagged_commit = "e90810b8df3e80c413d903f631643c716887138d";
+static const char *short_tag_id = "5da7760512a953e3c7c4e47e4392c7a4338fb729";
+static const char *short_tagged_commit = "4a5ed60bafcf4638b7c8356bd4ce1916bfede93c";
static git_repository *g_repo;
@@ -36,7 +38,7 @@ void test_object_tag_read__parse(void)
cl_git_pass(git_tag_lookup(&tag1, g_repo, &id1));
cl_assert_equal_s(git_tag_name(tag1), "test");
- cl_assert(git_tag_type(tag1) == GIT_OBJ_TAG);
+ cl_assert(git_tag_target_type(tag1) == GIT_OBJ_TAG);
cl_git_pass(git_tag_target((git_object **)&tag2, tag1));
cl_assert(tag2 != NULL);
@@ -83,3 +85,34 @@ void test_object_tag_read__parse_without_tagger(void)
git_commit_free(commit);
git_repository_free(bad_tag_repo);
}
+
+void test_object_tag_read__parse_without_message(void)
+{
+ // read and parse a tag without a message field
+ git_repository *short_tag_repo;
+ git_tag *short_tag;
+ git_commit *commit;
+ git_oid id, id_commit;
+
+ // TODO: This is a little messy
+ cl_git_pass(git_repository_open(&short_tag_repo, cl_fixture("short_tag.git")));
+
+ git_oid_fromstr(&id, short_tag_id);
+ git_oid_fromstr(&id_commit, short_tagged_commit);
+
+ cl_git_pass(git_tag_lookup(&short_tag, short_tag_repo, &id));
+ cl_assert(short_tag != NULL);
+
+ cl_assert_equal_s(git_tag_name(short_tag), "no_description");
+ cl_assert(git_oid_cmp(&id, git_tag_id(short_tag)) == 0);
+ cl_assert(short_tag->message == NULL);
+
+ cl_git_pass(git_tag_target((git_object **)&commit, short_tag));
+ cl_assert(commit != NULL);
+
+ cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0);
+
+ git_tag_free(short_tag);
+ git_commit_free(commit);
+ git_repository_free(short_tag_repo);
+}
diff --git a/tests-clar/object/tree/duplicateentries.c b/tests-clar/object/tree/duplicateentries.c
new file mode 100644
index 000000000..3052e2926
--- /dev/null
+++ b/tests-clar/object/tree/duplicateentries.c
@@ -0,0 +1,157 @@
+#include "clar_libgit2.h"
+#include "tree.h"
+
+static git_repository *_repo;
+
+void test_object_tree_duplicateentries__initialize(void) {
+ _repo = cl_git_sandbox_init("testrepo");
+}
+
+void test_object_tree_duplicateentries__cleanup(void) {
+ cl_git_sandbox_cleanup();
+}
+
+/*
+ * $ git show --format=raw refs/heads/dir
+ * commit 144344043ba4d4a405da03de3844aa829ae8be0e
+ * tree d52a8fe84ceedf260afe4f0287bbfca04a117e83
+ * parent cf80f8de9f1185bf3a05f993f6121880dd0cfbc9
+ * author Ben Straub <bstraub@github.com> 1343755506 -0700
+ * committer Ben Straub <bstraub@github.com> 1343755506 -0700
+ *
+ * Change a file mode
+ *
+ * diff --git a/a/b.txt b/a/b.txt
+ * old mode 100644
+ * new mode 100755
+ *
+ * $ git ls-tree d52a8fe84ceedf260afe4f0287bbfca04a117e83
+ * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README
+ * 040000 tree 4e0883eeeeebc1fb1735161cea82f7cb5fab7e63 a
+ * 100644 blob 45b983be36b73c0788dc9cbcb76cbb80fc7bb057 branch_file.txt
+ * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt
+ */
+
+static void tree_checker(
+ git_oid *tid,
+ const char *expected_sha,
+ git_filemode_t expected_filemode)
+{
+ git_tree *tree;
+ const git_tree_entry *entry;
+ git_oid oid;
+
+ cl_git_pass(git_tree_lookup(&tree, _repo, tid));
+ cl_assert_equal_i(1, git_tree_entrycount(tree));
+ entry = git_tree_entry_byindex(tree, 0);
+
+ cl_git_pass(git_oid_fromstr(&oid, expected_sha));
+
+ cl_assert_equal_i(0, git_oid_cmp(&oid, git_tree_entry_id(entry)));
+ cl_assert_equal_i(expected_filemode, git_tree_entry_filemode(entry));
+
+ git_tree_free(tree);
+}
+
+static void tree_creator(git_oid *out, void (*fn)(git_treebuilder *))
+{
+ git_treebuilder *builder;
+
+ cl_git_pass(git_treebuilder_create(&builder, NULL));
+
+ fn(builder);
+
+ cl_git_pass(git_treebuilder_write(out, _repo, builder));
+ git_treebuilder_free(builder);
+}
+
+static void two_blobs(git_treebuilder *bld)
+{
+ git_oid oid;
+ const git_tree_entry *entry;
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* blob oid (README) */
+
+ cl_git_pass(git_treebuilder_insert(
+ &entry, bld, "duplicate", &oid,
+ GIT_FILEMODE_BLOB));
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); /* blob oid (new.txt) */
+
+ cl_git_pass(git_treebuilder_insert(
+ &entry, bld, "duplicate", &oid,
+ GIT_FILEMODE_BLOB));
+}
+
+static void one_blob_and_one_tree(git_treebuilder *bld)
+{
+ git_oid oid;
+ const git_tree_entry *entry;
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* blob oid (README) */
+
+ cl_git_pass(git_treebuilder_insert(
+ &entry, bld, "duplicate", &oid,
+ GIT_FILEMODE_BLOB));
+
+ cl_git_pass(git_oid_fromstr(&oid,
+ "4e0883eeeeebc1fb1735161cea82f7cb5fab7e63")); /* tree oid (a) */
+
+ cl_git_pass(git_treebuilder_insert(
+ &entry, bld, "duplicate", &oid,
+ GIT_FILEMODE_TREE));
+}
+
+void test_object_tree_duplicateentries__cannot_create_a_duplicate_entry_through_the_treebuilder(void)
+{
+ git_oid tid;
+
+ tree_creator(&tid, two_blobs);
+ tree_checker(&tid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", GIT_FILEMODE_BLOB);
+
+ tree_creator(&tid, one_blob_and_one_tree);
+ tree_checker(&tid, "4e0883eeeeebc1fb1735161cea82f7cb5fab7e63", GIT_FILEMODE_TREE);
+}
+
+static void add_fake_conflicts(git_index *index)
+{
+ git_index_entry ancestor_entry, our_entry, their_entry;
+
+ memset(&ancestor_entry, 0x0, sizeof(git_index_entry));
+ memset(&our_entry, 0x0, sizeof(git_index_entry));
+ memset(&their_entry, 0x0, sizeof(git_index_entry));
+
+ ancestor_entry.path = "duplicate";
+ ancestor_entry.mode = GIT_FILEMODE_BLOB;
+ ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&ancestor_entry.oid, "a8233120f6ad708f843d861ce2b7228ec4e3dec6");
+
+ our_entry.path = "duplicate";
+ our_entry.mode = GIT_FILEMODE_BLOB;
+ ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&our_entry.oid, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057");
+
+ their_entry.path = "duplicate";
+ their_entry.mode = GIT_FILEMODE_BLOB;
+ ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT);
+ git_oid_fromstr(&their_entry.oid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd");
+
+ cl_git_pass(git_index_conflict_add(index, &ancestor_entry, &our_entry, &their_entry));
+}
+
+void test_object_tree_duplicateentries__cannot_create_a_duplicate_entry_building_a_tree_from_a_index_with_conflicts(void)
+{
+ git_index *index;
+ git_oid tid;
+
+ cl_git_pass(git_repository_index(&index, _repo));
+
+ add_fake_conflicts(index);
+
+ cl_assert_equal_i(GIT_EUNMERGED, git_index_write_tree(&tid, index));
+
+ git_index_free(index);
+}
diff --git a/tests-clar/object/tree/write.c b/tests-clar/object/tree/write.c
index 657bed289..cc5438b05 100644
--- a/tests-clar/object/tree/write.c
+++ b/tests-clar/object/tree/write.c
@@ -39,6 +39,12 @@ void test_object_tree_write__from_memory(void)
&bid, GIT_FILEMODE_BLOB));
cl_git_fail(git_treebuilder_insert(NULL, builder, "/",
&bid, GIT_FILEMODE_BLOB));
+ cl_git_fail(git_treebuilder_insert(NULL, builder, ".git",
+ &bid, GIT_FILEMODE_BLOB));
+ cl_git_fail(git_treebuilder_insert(NULL, builder, "..",
+ &bid, GIT_FILEMODE_BLOB));
+ cl_git_fail(git_treebuilder_insert(NULL, builder, ".",
+ &bid, GIT_FILEMODE_BLOB));
cl_git_fail(git_treebuilder_insert(NULL, builder, "folder/new.txt",
&bid, GIT_FILEMODE_BLOB));
diff --git a/tests-clar/odb/alternates.c b/tests-clar/odb/alternates.c
new file mode 100644
index 000000000..785d3bc84
--- /dev/null
+++ b/tests-clar/odb/alternates.c
@@ -0,0 +1,75 @@
+#include "clar_libgit2.h"
+#include "odb.h"
+#include "repository.h"
+
+static git_buf destpath, filepath;
+static const char *paths[] = {
+ "A.git", "B.git", "C.git", "D.git", "E.git", "F.git", "G.git"
+};
+static git_filebuf file;
+static git_repository *repo;
+
+void test_odb_alternates__cleanup(void)
+{
+ git_buf_free(&destpath);
+ git_buf_free(&filepath);
+}
+
+static void init_linked_repo(const char *path, const char *alternate)
+{
+ git_buf_clear(&destpath);
+ git_buf_clear(&filepath);
+
+ cl_git_pass(git_repository_init(&repo, path, 1));
+ cl_git_pass(git_path_prettify(&destpath, alternate, NULL));
+ cl_git_pass(git_buf_joinpath(&destpath, destpath.ptr, "objects"));
+ cl_git_pass(git_buf_joinpath(&filepath, git_repository_path(repo), "objects/info"));
+ cl_git_pass(git_futils_mkdir(filepath.ptr, NULL, 0755, GIT_MKDIR_PATH));
+ cl_git_pass(git_buf_joinpath(&filepath, filepath.ptr , "alternates"));
+
+ cl_git_pass(git_filebuf_open(&file, git_buf_cstr(&filepath), 0));
+ git_filebuf_printf(&file, "%s\n", git_buf_cstr(&destpath));
+ cl_git_pass(git_filebuf_commit(&file, 0644));
+
+ git_repository_free(repo);
+}
+
+void test_odb_alternates__chained(void)
+{
+ git_commit *commit;
+ git_oid oid;
+
+ /* Set the alternate A -> testrepo.git */
+ init_linked_repo(paths[0], cl_fixture("testrepo.git"));
+
+ /* Set the alternate B -> A */
+ init_linked_repo(paths[1], paths[0]);
+
+ /* Now load B and see if we can find an object from testrepo.git */
+ cl_git_pass(git_repository_open(&repo, paths[1]));
+ git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ cl_git_pass(git_commit_lookup(&commit, repo, &oid));
+ git_commit_free(commit);
+ git_repository_free(repo);
+}
+
+void test_odb_alternates__long_chain(void)
+{
+ git_commit *commit;
+ git_oid oid;
+ size_t i;
+
+ /* Set the alternate A -> testrepo.git */
+ init_linked_repo(paths[0], cl_fixture("testrepo.git"));
+
+ /* Set up the five-element chain */
+ for (i = 1; i < ARRAY_SIZE(paths); i++) {
+ init_linked_repo(paths[i], paths[i-1]);
+ }
+
+ /* Now load the last one and see if we can find an object from testrepo.git */
+ cl_git_pass(git_repository_open(&repo, paths[ARRAY_SIZE(paths)-1]));
+ git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
+ cl_git_fail(git_commit_lookup(&commit, repo, &oid));
+ git_repository_free(repo);
+}
diff --git a/tests-clar/pack/packbuilder.c b/tests-clar/pack/packbuilder.c
index fa7bec14e..208141c27 100644
--- a/tests-clar/pack/packbuilder.c
+++ b/tests-clar/pack/packbuilder.c
@@ -28,12 +28,12 @@ void test_pack_packbuilder__cleanup(void)
git_packbuilder_free(_packbuilder);
git_revwalk_free(_revwalker);
git_indexer_free(_indexer);
+ _indexer = NULL;
git_repository_free(_repo);
}
-void test_pack_packbuilder__create_pack(void)
+static void seed_packbuilder(void)
{
- git_indexer_stats stats;
git_oid oid, *o;
unsigned int i;
@@ -58,10 +58,37 @@ void test_pack_packbuilder__create_pack(void)
git_commit_tree_oid((git_commit *)obj)));
git_object_free(obj);
}
+}
+void test_pack_packbuilder__create_pack(void)
+{
+ git_transfer_progress stats;
+
+ seed_packbuilder();
cl_git_pass(git_packbuilder_write(_packbuilder, "testpack.pack"));
cl_git_pass(git_indexer_new(&_indexer, "testpack.pack"));
cl_git_pass(git_indexer_run(_indexer, &stats));
cl_git_pass(git_indexer_write(_indexer));
}
+
+static git_transfer_progress stats;
+static int foreach_cb(void *buf, size_t len, void *payload)
+{
+ git_indexer_stream *idx = (git_indexer_stream *) payload;
+
+ cl_git_pass(git_indexer_stream_add(idx, buf, len, &stats));
+
+ return 0;
+}
+
+void test_pack_packbuilder__foreach(void)
+{
+ git_indexer_stream *idx;
+
+ seed_packbuilder();
+ cl_git_pass(git_indexer_stream_new(&idx, ".", NULL, NULL));
+ cl_git_pass(git_packbuilder_foreach(_packbuilder, foreach_cb, idx));
+ cl_git_pass(git_indexer_stream_finalize(idx, &stats));
+ git_indexer_stream_free(idx);
+}
diff --git a/tests-clar/refs/branches/delete.c b/tests-clar/refs/branches/delete.c
index 4e9c70904..da7db13fc 100644
--- a/tests-clar/refs/branches/delete.c
+++ b/tests-clar/refs/branches/delete.c
@@ -1,6 +1,7 @@
#include "clar_libgit2.h"
#include "refs.h"
#include "repo/repo_helpers.h"
+#include "config/config_helpers.h"
static git_repository *repo;
static git_reference *fake_remote;
@@ -90,3 +91,17 @@ void test_refs_branches_delete__can_delete_a_remote_branch(void)
cl_git_pass(git_branch_lookup(&branch, repo, "nulltoken/master", GIT_BRANCH_REMOTE));
cl_git_pass(git_branch_delete(branch));
}
+
+void test_refs_branches_delete__deleting_a_branch_removes_related_configuration_data(void)
+{
+ git_reference *branch;
+
+ assert_config_entry_existence(repo, "branch.track-local.remote", true);
+ assert_config_entry_existence(repo, "branch.track-local.merge", true);
+
+ cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL));
+ cl_git_pass(git_branch_delete(branch));
+
+ assert_config_entry_existence(repo, "branch.track-local.remote", false);
+ assert_config_entry_existence(repo, "branch.track-local.merge", false);
+} \ No newline at end of file
diff --git a/tests-clar/refs/branches/ishead.c b/tests-clar/refs/branches/ishead.c
index ab17482b8..52a0a1941 100644
--- a/tests-clar/refs/branches/ishead.c
+++ b/tests-clar/refs/branches/ishead.c
@@ -39,6 +39,22 @@ void test_refs_branches_ishead__can_properly_handle_orphaned_HEAD(void)
repo = NULL;
}
+void test_refs_branches_ishead__can_properly_handle_missing_HEAD(void)
+{
+ git_repository_free(repo);
+
+ repo = cl_git_sandbox_init("testrepo.git");
+
+ delete_head(repo);
+
+ cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master"));
+
+ cl_assert_equal_i(false, git_branch_is_head(branch));
+
+ cl_git_sandbox_cleanup();
+ repo = NULL;
+}
+
void test_refs_branches_ishead__can_tell_if_a_branch_is_not_pointed_at_by_HEAD(void)
{
cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/br2"));
diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c
index 62b6042c6..042469016 100644
--- a/tests-clar/refs/branches/move.c
+++ b/tests-clar/refs/branches/move.c
@@ -1,5 +1,6 @@
#include "clar_libgit2.h"
#include "refs.h"
+#include "config/config_helpers.h"
static git_repository *repo;
static git_reference *ref;
@@ -63,6 +64,27 @@ void test_refs_branches_move__can_force_move_over_an_existing_branch(void)
cl_git_pass(git_branch_move(ref, "master", 1));
}
+void test_refs_branches_move__moving_a_branch_moves_related_configuration_data(void)
+{
+ git_reference *branch;
+
+ cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL));
+
+ assert_config_entry_existence(repo, "branch.track-local.remote", true);
+ assert_config_entry_existence(repo, "branch.track-local.merge", true);
+ assert_config_entry_existence(repo, "branch.moved.remote", false);
+ assert_config_entry_existence(repo, "branch.moved.merge", false);
+
+ cl_git_pass(git_branch_move(branch, "moved", 0));
+
+ assert_config_entry_existence(repo, "branch.track-local.remote", false);
+ assert_config_entry_existence(repo, "branch.track-local.merge", false);
+ assert_config_entry_existence(repo, "branch.moved.remote", true);
+ assert_config_entry_existence(repo, "branch.moved.merge", true);
+
+ git_reference_free(branch);
+}
+
void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD(void)
{
git_reference *branch;
diff --git a/tests-clar/refs/reflog/drop.c b/tests-clar/refs/reflog/drop.c
index 86781c041..512e8ba02 100644
--- a/tests-clar/refs/reflog/drop.c
+++ b/tests-clar/refs/reflog/drop.c
@@ -43,35 +43,28 @@ void test_refs_reflog_drop__can_drop_an_entry(void)
void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void)
{
- const git_reflog_entry *before_previous, *before_next;
- const git_reflog_entry *after_next;
- git_oid before_next_old_oid;
+ const git_reflog_entry *before_current;
+ const git_reflog_entry *after_current;
+ git_oid before_current_old_oid, before_current_cur_oid;
cl_assert(entrycount > 4);
- before_previous = git_reflog_entry_byindex(g_reflog, 3);
- before_next = git_reflog_entry_byindex(g_reflog, 1);
- git_oid_cpy(&before_next_old_oid, &before_next->oid_old);
+ before_current = git_reflog_entry_byindex(g_reflog, 1);
- cl_git_pass(git_reflog_drop(g_reflog, 2, 1));
- cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
+ git_oid_cpy(&before_current_old_oid, &before_current->oid_old);
+ git_oid_cpy(&before_current_cur_oid, &before_current->oid_cur);
- after_next = git_reflog_entry_byindex(g_reflog, 1);
+ cl_git_pass(git_reflog_drop(g_reflog, 1, 1));
- cl_assert_equal_i(0, git_oid_cmp(&before_next->oid_cur, &after_next->oid_cur));
- cl_assert(git_oid_cmp(&before_next_old_oid, &after_next->oid_old) != 0);
- cl_assert_equal_i(0, git_oid_cmp(&before_previous->oid_cur, &after_next->oid_old));
-}
+ cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
-void test_refs_reflog_drop__can_drop_the_first_entry(void)
-{
- cl_assert(entrycount > 2);
+ after_current = git_reflog_entry_byindex(g_reflog, 0);
- cl_git_pass(git_reflog_drop(g_reflog, 0, 0));
- cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
+ cl_assert_equal_i(0, git_oid_cmp(&before_current_old_oid, &after_current->oid_old));
+ cl_assert(0 != git_oid_cmp(&before_current_cur_oid, &after_current->oid_cur));
}
-void test_refs_reflog_drop__can_drop_the_last_entry(void)
+void test_refs_reflog_drop__can_drop_the_oldest_entry(void)
{
const git_reflog_entry *entry;
@@ -84,7 +77,7 @@ void test_refs_reflog_drop__can_drop_the_last_entry(void)
cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0);
}
-void test_refs_reflog_drop__can_drop_the_last_entry_and_rewrite_the_log_history(void)
+void test_refs_reflog_drop__can_drop_the_oldest_entry_and_rewrite_the_log_history(void)
{
const git_reflog_entry *entry;
@@ -102,8 +95,8 @@ void test_refs_reflog_drop__can_drop_all_the_entries(void)
cl_assert(--entrycount > 0);
do {
- cl_git_pass(git_reflog_drop(g_reflog, --entrycount, 1));
- } while (entrycount > 0);
+ cl_git_pass(git_reflog_drop(g_reflog, 0, 1));
+ } while (--entrycount > 0);
cl_git_pass(git_reflog_drop(g_reflog, 0, 1));
@@ -117,7 +110,7 @@ void test_refs_reflog_drop__can_persist_deletion_on_disk(void)
cl_assert(entrycount > 2);
cl_git_pass(git_reference_lookup(&ref, g_repo, g_reflog->ref_name));
- cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1));
+ cl_git_pass(git_reflog_drop(g_reflog, 0, 1));
cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog));
cl_git_pass(git_reflog_write(g_reflog));
diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c
index 20f08f523..09b935692 100644
--- a/tests-clar/refs/reflog/reflog.c
+++ b/tests-clar/refs/reflog/reflog.c
@@ -68,13 +68,13 @@ void test_refs_reflog_reflog__append_then_read(void)
cl_git_pass(git_reflog_read(&reflog, lookedup_ref));
cl_assert_equal_i(2, git_reflog_entrycount(reflog));
- entry = git_reflog_entry_byindex(reflog, 0);
+ entry = git_reflog_entry_byindex(reflog, 1);
assert_signature(committer, entry->committer);
cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0);
cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0);
cl_assert(entry->msg == NULL);
- entry = git_reflog_entry_byindex(reflog, 1);
+ entry = git_reflog_entry_byindex(reflog, 0);
assert_signature(committer, entry->committer);
cl_assert(git_oid_cmp(&oid, &entry->oid_old) == 0);
cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0);
diff --git a/tests-clar/repo/discover.c b/tests-clar/repo/discover.c
index b5afab75a..3d9aeedd7 100644
--- a/tests-clar/repo/discover.c
+++ b/tests-clar/repo/discover.c
@@ -135,7 +135,7 @@ void test_repo_discover__0(void)
ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path);
ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, repository_path);
- cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_RMDIR_REMOVE_FILES));
git_repository_free(repo);
git_buf_free(&ceiling_dirs_buf);
}
diff --git a/tests-clar/repo/getters.c b/tests-clar/repo/getters.c
index ffcd171f2..b372f5b70 100644
--- a/tests-clar/repo/getters.c
+++ b/tests-clar/repo/getters.c
@@ -1,26 +1,26 @@
#include "clar_libgit2.h"
-void test_repo_getters__initialize(void)
+void test_repo_getters__is_empty_correctly_deals_with_pristine_looking_repos(void)
{
- cl_fixture_sandbox("testrepo.git");
-}
+ git_repository *repo;
-void test_repo_getters__cleanup(void)
-{
- cl_fixture_cleanup("testrepo.git");
+ repo = cl_git_sandbox_init("empty_bare.git");
+ cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt");
+
+ cl_assert_equal_i(true, git_repository_is_empty(repo));
+
+ cl_git_sandbox_cleanup();
}
-void test_repo_getters__empty(void)
+void test_repo_getters__is_empty_can_detect_used_repositories(void)
{
- git_repository *repo_empty, *repo_normal;
+ git_repository *repo;
- cl_git_pass(git_repository_open(&repo_normal, cl_fixture("testrepo.git")));
- cl_assert(git_repository_is_empty(repo_normal) == 0);
- git_repository_free(repo_normal);
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
- cl_git_pass(git_repository_open(&repo_empty, cl_fixture("empty_bare.git")));
- cl_assert(git_repository_is_empty(repo_empty) == 1);
- git_repository_free(repo_empty);
+ cl_assert_equal_i(false, git_repository_is_empty(repo));
+
+ git_repository_free(repo);
}
void test_repo_getters__retrieving_the_odb_honors_the_refcount(void)
@@ -28,7 +28,7 @@ void test_repo_getters__retrieving_the_odb_honors_the_refcount(void)
git_odb *odb;
git_repository *repo;
- cl_git_pass(git_repository_open(&repo, "testrepo.git"));
+ cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git")));
cl_git_pass(git_repository_odb(&odb, repo));
cl_assert(((git_refcount *)odb)->refcount == 2);
diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c
index 58f525e2b..551e834f2 100644
--- a/tests-clar/repo/head.c
+++ b/tests-clar/repo/head.c
@@ -1,6 +1,7 @@
#include "clar_libgit2.h"
#include "refs.h"
#include "repo_helpers.h"
+#include "posix.h"
git_repository *repo;
@@ -178,6 +179,15 @@ void test_repo_head__retrieving_an_orphaned_head_returns_GIT_EORPHANEDHEAD(void)
cl_assert_equal_i(GIT_EORPHANEDHEAD, git_repository_head(&head, repo));
}
+void test_repo_head__retrieving_a_missing_head_returns_GIT_ENOTFOUND(void)
+{
+ git_reference *head;
+
+ delete_head(repo);
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo));
+}
+
void test_repo_head__can_tell_if_an_orphaned_head_is_detached(void)
{
make_head_orphaned(repo, NON_EXISTING_HEAD);
diff --git a/tests-clar/repo/open.c b/tests-clar/repo/open.c
index ef912fa0e..7f93ae91a 100644
--- a/tests-clar/repo/open.c
+++ b/tests-clar/repo/open.c
@@ -7,7 +7,7 @@ void test_repo_open__cleanup(void)
cl_git_sandbox_cleanup();
if (git_path_isdir("alternate"))
- git_futils_rmdir_r("alternate", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
+ git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES);
}
void test_repo_open__bare_empty_repo(void)
@@ -202,8 +202,8 @@ void test_repo_open__bad_gitlinks(void)
cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL));
}
- git_futils_rmdir_r("invalid", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
- git_futils_rmdir_r("invalid2", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
+ git_futils_rmdir_r("invalid", NULL, GIT_RMDIR_REMOVE_FILES);
+ git_futils_rmdir_r("invalid2", NULL, GIT_RMDIR_REMOVE_FILES);
}
#ifdef GIT_WIN32
diff --git a/tests-clar/repo/repo_helpers.c b/tests-clar/repo/repo_helpers.c
index 35271feaa..19ab38ee3 100644
--- a/tests-clar/repo/repo_helpers.c
+++ b/tests-clar/repo/repo_helpers.c
@@ -1,6 +1,7 @@
#include "clar_libgit2.h"
#include "refs.h"
#include "repo_helpers.h"
+#include "posix.h"
void make_head_orphaned(git_repository* repo, const char *target)
{
@@ -9,3 +10,13 @@ void make_head_orphaned(git_repository* repo, const char *target)
cl_git_pass(git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, target, 1));
git_reference_free(head);
}
+
+void delete_head(git_repository* repo)
+{
+ git_buf head_path = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(&head_path, git_repository_path(repo), GIT_HEAD_FILE));
+ cl_git_pass(p_unlink(git_buf_cstr(&head_path)));
+
+ git_buf_free(&head_path);
+}
diff --git a/tests-clar/repo/repo_helpers.h b/tests-clar/repo/repo_helpers.h
index e6aeb4873..09b5cac84 100644
--- a/tests-clar/repo/repo_helpers.h
+++ b/tests-clar/repo/repo_helpers.h
@@ -3,3 +3,4 @@
#define NON_EXISTING_HEAD "refs/heads/hide/and/seek"
extern void make_head_orphaned(git_repository* repo, const char *target);
+extern void delete_head(git_repository* repo);
diff --git a/tests-clar/repo/state.c b/tests-clar/repo/state.c
new file mode 100644
index 000000000..5a0a5f360
--- /dev/null
+++ b/tests-clar/repo/state.c
@@ -0,0 +1,96 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "refs.h"
+#include "posix.h"
+#include "fileops.h"
+
+static git_repository *_repo;
+static git_buf _path;
+
+void test_repo_state__initialize(void)
+{
+ _repo = cl_git_sandbox_init("testrepo.git");
+}
+
+void test_repo_state__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+ git_buf_free(&_path);
+}
+
+static void setup_simple_state(const char *filename)
+{
+ cl_git_pass(git_buf_joinpath(&_path, git_repository_path(_repo), filename));
+ git_futils_mkpath2file(git_buf_cstr(&_path), 0777);
+ cl_git_mkfile(git_buf_cstr(&_path), "dummy");
+}
+
+static void assert_repo_state(git_repository_state_t state)
+{
+ cl_assert_equal_i(state, git_repository_state(_repo));
+}
+
+void test_repo_state__none_with_HEAD_attached(void)
+{
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__none_with_HEAD_detached(void)
+{
+ cl_git_pass(git_repository_detach_head(_repo));
+ assert_repo_state(GIT_REPOSITORY_STATE_NONE);
+}
+
+void test_repo_state__merge(void)
+{
+ setup_simple_state(GIT_MERGE_HEAD_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_MERGE);
+}
+
+void test_repo_state__revert(void)
+{
+ setup_simple_state(GIT_REVERT_HEAD_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_REVERT);
+}
+
+void test_repo_state__cherry_pick(void)
+{
+ setup_simple_state(GIT_CHERRY_PICK_HEAD_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_CHERRY_PICK);
+}
+
+void test_repo_state__bisect(void)
+{
+ setup_simple_state(GIT_BISECT_LOG_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_BISECT);
+}
+
+void test_repo_state__rebase_interactive(void)
+{
+ setup_simple_state(GIT_REBASE_MERGE_INTERACTIVE_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_REBASE_INTERACTIVE);
+}
+
+void test_repo_state__rebase_merge(void)
+{
+ setup_simple_state(GIT_REBASE_MERGE_DIR "whatever");
+ assert_repo_state(GIT_REPOSITORY_STATE_REBASE_MERGE);
+}
+
+void test_repo_state__rebase(void)
+{
+ setup_simple_state(GIT_REBASE_APPLY_REBASING_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_REBASE);
+}
+
+void test_repo_state__apply_mailbox(void)
+{
+ setup_simple_state(GIT_REBASE_APPLY_APPLYING_FILE);
+ assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX);
+}
+
+void test_repo_state__apply_mailbox_or_rebase(void)
+{
+ setup_simple_state(GIT_REBASE_APPLY_DIR "whatever");
+ assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE);
+}
diff --git a/tests-clar/reset/hard.c b/tests-clar/reset/hard.c
index fdab9c536..bddbd17d7 100644
--- a/tests-clar/reset/hard.c
+++ b/tests-clar/reset/hard.c
@@ -19,30 +19,71 @@ void test_reset_hard__cleanup(void)
cl_git_sandbox_cleanup();
}
-void test_reset_hard__resetting_culls_empty_directories(void)
+static int strequal_ignore_eol(const char *exp, const char *str)
{
- git_buf subdir_path = GIT_BUF_INIT;
- git_buf subfile_path = GIT_BUF_INIT;
- git_buf newdir_path = GIT_BUF_INIT;
+ while (*exp && *str) {
+ if (*exp != *str) {
+ while (*exp == '\r' || *exp == '\n') ++exp;
+ while (*str == '\r' || *str == '\n') ++str;
+ if (*exp != *str)
+ return false;
+ } else {
+ exp++; str++;
+ }
+ }
+ return (!*exp && !*str);
+}
- cl_git_pass(git_buf_joinpath(&newdir_path, git_repository_workdir(repo), "newdir/"));
+void test_reset_hard__resetting_reverts_modified_files(void)
+{
+ git_buf path = GIT_BUF_INIT, content = GIT_BUF_INIT;
+ int i;
+ static const char *files[4] = {
+ "current_file",
+ "modified_file",
+ "staged_new_file",
+ "staged_changes_modified_file" };
+ static const char *before[4] = {
+ "current_file\n",
+ "modified_file\nmodified_file\n",
+ "staged_new_file\n",
+ "staged_changes_modified_file\nstaged_changes_modified_file\nstaged_changes_modified_file\n"
+ };
+ static const char *after[4] = {
+ "current_file\n",
+ "modified_file\n",
+ /* wrong value because reset is still slightly incorrect */
+ "staged_new_file\n",
+ /* right value: NULL, */
+ "staged_changes_modified_file\n"
+ };
+ const char *wd = git_repository_workdir(repo);
- cl_git_pass(git_buf_joinpath(&subfile_path, git_buf_cstr(&newdir_path), "with/nested/file.txt"));
- cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&subfile_path), 0755));
- cl_git_mkfile(git_buf_cstr(&subfile_path), "all anew...\n");
+ cl_assert(wd);
- cl_git_pass(git_buf_joinpath(&subdir_path, git_repository_workdir(repo), "subdir/"));
- cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == true);
+ for (i = 0; i < 4; ++i) {
+ cl_git_pass(git_buf_joinpath(&path, wd, files[i]));
+ cl_git_pass(git_futils_readbuffer(&content, path.ptr));
+ cl_assert_equal_s(before[i], content.ptr);
+ }
+
+ retrieve_target_from_oid(
+ &target, repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f");
- retrieve_target_from_oid(&target, repo, "0017bd4ab1ec30440b17bae1680cff124ab5f1f6");
cl_git_pass(git_reset(repo, target, GIT_RESET_HARD));
- cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == false);
- cl_assert(git_path_isdir(git_buf_cstr(&newdir_path)) == false);
+ for (i = 0; i < 4; ++i) {
+ cl_git_pass(git_buf_joinpath(&path, wd, files[i]));
+ if (after[i]) {
+ cl_git_pass(git_futils_readbuffer(&content, path.ptr));
+ cl_assert(strequal_ignore_eol(after[i], content.ptr));
+ } else {
+ cl_assert(!git_path_exists(path.ptr));
+ }
+ }
- git_buf_free(&subdir_path);
- git_buf_free(&subfile_path);
- git_buf_free(&newdir_path);
+ git_buf_free(&content);
+ git_buf_free(&path);
}
void test_reset_hard__cannot_reset_in_a_bare_repository(void)
@@ -58,3 +99,38 @@ void test_reset_hard__cannot_reset_in_a_bare_repository(void)
git_repository_free(bare);
}
+
+void test_reset_hard__cleans_up_merge(void)
+{
+ git_buf merge_head_path = GIT_BUF_INIT,
+ merge_msg_path = GIT_BUF_INIT,
+ merge_mode_path = GIT_BUF_INIT,
+ orig_head_path = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD"));
+ cl_git_mkfile(git_buf_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n");
+
+ cl_git_pass(git_buf_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MSG"));
+ cl_git_mkfile(git_buf_cstr(&merge_head_path), "Merge commit 0017bd4ab1ec30440b17bae1680cff124ab5f1f6\n");
+
+ cl_git_pass(git_buf_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MODE"));
+ cl_git_mkfile(git_buf_cstr(&merge_head_path), "");
+
+ cl_git_pass(git_buf_joinpath(&orig_head_path, git_repository_path(repo), "ORIG_HEAD"));
+ cl_git_mkfile(git_buf_cstr(&orig_head_path), "0017bd4ab1ec30440b17bae1680cff124ab5f1f6");
+
+ retrieve_target_from_oid(&target, repo, "0017bd4ab1ec30440b17bae1680cff124ab5f1f6");
+ cl_git_pass(git_reset(repo, target, GIT_RESET_HARD));
+
+ cl_assert(!git_path_exists(git_buf_cstr(&merge_head_path)));
+ cl_assert(!git_path_exists(git_buf_cstr(&merge_msg_path)));
+ cl_assert(!git_path_exists(git_buf_cstr(&merge_mode_path)));
+
+ cl_assert(git_path_exists(git_buf_cstr(&orig_head_path)));
+ cl_git_pass(p_unlink(git_buf_cstr(&orig_head_path)));
+
+ git_buf_free(&merge_head_path);
+ git_buf_free(&merge_msg_path);
+ git_buf_free(&merge_mode_path);
+ git_buf_free(&orig_head_path);
+}
diff --git a/tests-clar/reset/soft.c b/tests-clar/reset/soft.c
index 1872baf3b..d51e3f1f1 100644
--- a/tests-clar/reset/soft.c
+++ b/tests-clar/reset/soft.c
@@ -1,5 +1,7 @@
#include "clar_libgit2.h"
+#include "posix.h"
#include "reset_helpers.h"
+#include "path.h"
#include "repo/repo_helpers.h"
static git_repository *repo;
@@ -110,3 +112,19 @@ void test_reset_soft__resetting_against_an_orphaned_head_repo_makes_the_head_no_
git_reference_free(head);
}
+
+void test_reset_soft__fails_when_merging(void)
+{
+ git_buf merge_head_path = GIT_BUF_INIT;
+
+ cl_git_pass(git_repository_detach_head(repo));
+ cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD"));
+ cl_git_mkfile(git_buf_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n");
+
+ retrieve_target_from_oid(&target, repo, KNOWN_COMMIT_IN_BARE_REPO);
+
+ cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT));
+ cl_git_pass(p_unlink(git_buf_cstr(&merge_head_path)));
+
+ git_buf_free(&merge_head_path);
+}
diff --git a/tests-clar/resources/config/config11 b/tests-clar/resources/config/config11
index 880c94589..7331862a5 100644
--- a/tests-clar/resources/config/config11
+++ b/tests-clar/resources/config/config11
@@ -1,3 +1,5 @@
[remote "fancy"]
url = git://github.com/libgit2/libgit2
url = git://git.example.com/libgit2
+
+
diff --git a/tests-clar/resources/config/config4 b/tests-clar/resources/config/config4
index 741fa0ffd..9dd40419e 100644
--- a/tests-clar/resources/config/config4
+++ b/tests-clar/resources/config/config4
@@ -1,3 +1,5 @@
# A variable name on its own is valid
[some.section]
variable
+# A variable and '=' is accepted, but it's not considered true
+ variableeq =
diff --git a/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG b/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG
new file mode 100644
index 000000000..1f7391f92
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG
@@ -0,0 +1 @@
+master
diff --git a/tests-clar/resources/mergedrepo/.gitted/HEAD b/tests-clar/resources/mergedrepo/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD b/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD
new file mode 100644
index 000000000..a5bdf6e40
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD
@@ -0,0 +1 @@
+e2809157a7766f272e4cfe26e61ef2678a5357ff
diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE b/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE
diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG b/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG
new file mode 100644
index 000000000..7c4d1f5a9
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG
@@ -0,0 +1,5 @@
+Merge branch 'branch'
+
+Conflicts:
+ conflicts-one.txt
+ conflicts-two.txt
diff --git a/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD b/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD
new file mode 100644
index 000000000..13d4d6721
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD
@@ -0,0 +1 @@
+3a34580a35add43a4cf361e8e9a30060a905c876
diff --git a/tests-clar/resources/mergedrepo/.gitted/config b/tests-clar/resources/mergedrepo/.gitted/config
new file mode 100644
index 000000000..af107929f
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/config
@@ -0,0 +1,6 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
diff --git a/tests-clar/resources/mergedrepo/.gitted/description b/tests-clar/resources/mergedrepo/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests-clar/resources/mergedrepo/.gitted/index b/tests-clar/resources/mergedrepo/.gitted/index
new file mode 100644
index 000000000..3d29f78e7
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/index
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/info/exclude b/tests-clar/resources/mergedrepo/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.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/mergedrepo/.gitted/logs/HEAD b/tests-clar/resources/mergedrepo/.gitted/logs/HEAD
new file mode 100644
index 000000000..a385da67b
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/logs/HEAD
@@ -0,0 +1,5 @@
+0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371828 -0500 commit (initial): initial
+9a05ccb4e0f948de03128e095f39dae6976751c5 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371835 -0500 checkout: moving from master to branch
+9a05ccb4e0f948de03128e095f39dae6976751c5 e2809157a7766f272e4cfe26e61ef2678a5357ff Edward Thomson <ethomson@edwardthomson.com> 1351371872 -0500 commit: branch
+e2809157a7766f272e4cfe26e61ef2678a5357ff 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371873 -0500 checkout: moving from branch to master
+9a05ccb4e0f948de03128e095f39dae6976751c5 3a34580a35add43a4cf361e8e9a30060a905c876 Edward Thomson <ethomson@edwardthomson.com> 1351372106 -0500 commit: master
diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch
new file mode 100644
index 000000000..26a5e8dc5
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371835 -0500 branch: Created from HEAD
+9a05ccb4e0f948de03128e095f39dae6976751c5 e2809157a7766f272e4cfe26e61ef2678a5357ff Edward Thomson <ethomson@edwardthomson.com> 1351371872 -0500 commit: branch
diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..425f7bd89
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371828 -0500 commit (initial): initial
+9a05ccb4e0f948de03128e095f39dae6976751c5 3a34580a35add43a4cf361e8e9a30060a905c876 Edward Thomson <ethomson@edwardthomson.com> 1351372106 -0500 commit: master
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 b/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712
new file mode 100644
index 000000000..9232f79d9
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 b/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91
new file mode 100644
index 000000000..3e124d9a4
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 b/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81
new file mode 100644
index 000000000..7bb19c873
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e b/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e
new file mode 100644
index 000000000..487bcffb1
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 b/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6
new file mode 100644
index 000000000..2eb3954fc
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 b/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534
new file mode 100644
index 000000000..ebe83ccb2
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 b/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876
new file mode 100644
index 000000000..0d4095ffc
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876
@@ -0,0 +1,2 @@
+xK
+1D]}Dx/O"F2oo<*ZoљuIhhrl"r YT8'#vm0.¨.:.#+9R^nG~[=VjR"IjD۔7|N` \ No newline at end of file
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c b/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c
new file mode 100644
index 000000000..33389c302
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 b/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3
new file mode 100644
index 000000000..5361ea685
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da b/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da
new file mode 100644
index 000000000..a60da877c
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad b/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad
new file mode 100644
index 000000000..85e84d71e
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 b/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399
new file mode 100644
index 000000000..b16b521e6
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 b/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8
new file mode 100644
index 000000000..7c4e85ffb
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 b/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4
new file mode 100644
index 000000000..65173fc4d
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 b/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71
new file mode 100644
index 000000000..162fa4455
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c b/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c
new file mode 100644
index 000000000..77a519f55
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda b/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda
new file mode 100644
index 000000000..f624cd4f1
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 b/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673
new file mode 100644
index 000000000..096474c03
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a b/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a
new file mode 100644
index 000000000..a413bc6b0
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 b/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2
new file mode 100644
index 000000000..3ac8f6018
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 b/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8
new file mode 100644
index 000000000..589a5ae9b
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 b/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9
new file mode 100644
index 000000000..6503985e3
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 b/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50
new file mode 100644
index 000000000..2eaa80838
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 b/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5
new file mode 100644
index 000000000..7373a80d8
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5
@@ -0,0 +1 @@
+x !Dm@ c6q##Ay/ ܁:#$ltH:闄*DXhV} ˷n[-K_;Z@J GԈbq3"go@I \ No newline at end of file
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c b/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c
new file mode 100644
index 000000000..c5a651f97
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d b/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d
new file mode 100644
index 000000000..3e14b5dc8
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 b/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51
new file mode 100644
index 000000000..a641adc2e
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff b/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff
new file mode 100644
index 000000000..fa86662e0
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff
@@ -0,0 +1,3 @@
+xK
+1D]t> xNq1(]{Pe mٍ.S0[Dcd
+ŅbMԝCgd@>glX].$!0*zu})/E_<ڪO:WځrơqѤh@mt;;5uZyVo\M \ No newline at end of file
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 b/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478
new file mode 100644
index 000000000..c9841c698
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 b/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406
new file mode 100644
index 000000000..cd587dbec
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406
Binary files differ
diff --git a/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch b/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch
new file mode 100644
index 000000000..a5bdf6e40
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch
@@ -0,0 +1 @@
+e2809157a7766f272e4cfe26e61ef2678a5357ff
diff --git a/tests-clar/resources/mergedrepo/.gitted/refs/heads/master b/tests-clar/resources/mergedrepo/.gitted/refs/heads/master
new file mode 100644
index 000000000..13d4d6721
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/.gitted/refs/heads/master
@@ -0,0 +1 @@
+3a34580a35add43a4cf361e8e9a30060a905c876
diff --git a/tests-clar/resources/mergedrepo/conflicts-one.txt b/tests-clar/resources/mergedrepo/conflicts-one.txt
new file mode 100644
index 000000000..8aad34cc8
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/conflicts-one.txt
@@ -0,0 +1,5 @@
+<<<<<<< HEAD
+This is most certainly a conflict!
+=======
+This is a conflict!!!
+>>>>>>> branch
diff --git a/tests-clar/resources/mergedrepo/conflicts-two.txt b/tests-clar/resources/mergedrepo/conflicts-two.txt
new file mode 100644
index 000000000..e62cac5c8
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/conflicts-two.txt
@@ -0,0 +1,5 @@
+<<<<<<< HEAD
+This is without question another conflict!
+=======
+This is another conflict!!!
+>>>>>>> branch
diff --git a/tests-clar/resources/mergedrepo/one.txt b/tests-clar/resources/mergedrepo/one.txt
new file mode 100644
index 000000000..75938de1e
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/one.txt
@@ -0,0 +1,10 @@
+This is file one!
+This is file one.
+This is file one.
+This is file one.
+This is file one.
+This is file one.
+This is file one.
+This is file one.
+This is file one.
+This is file one!
diff --git a/tests-clar/resources/mergedrepo/two.txt b/tests-clar/resources/mergedrepo/two.txt
new file mode 100644
index 000000000..7b26923aa
--- /dev/null
+++ b/tests-clar/resources/mergedrepo/two.txt
@@ -0,0 +1,12 @@
+This is file two!
+This is file two.
+This is file two.
+This is file two.
+This is file two.
+This is file two.
+This is file two.
+This is file two.
+This is file two.
+This is file two.
+This is file two.
+This is file two!
diff --git a/tests-clar/resources/partial-testrepo/.gitted/HEAD b/tests-clar/resources/partial-testrepo/.gitted/HEAD
new file mode 100644
index 000000000..4bfb9c93f
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/dir
diff --git a/tests-clar/resources/partial-testrepo/.gitted/config b/tests-clar/resources/partial-testrepo/.gitted/config
new file mode 100644
index 000000000..99abaab97
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/config
@@ -0,0 +1,7 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+[branch "dir"]
diff --git a/tests-clar/resources/partial-testrepo/.gitted/index b/tests-clar/resources/partial-testrepo/.gitted/index
new file mode 100644
index 000000000..4f241f914
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/index
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
new file mode 100644
index 000000000..cedb2a22e
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e b/tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e
new file mode 100644
index 000000000..b7d944fa1
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 b/tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925
new file mode 100644
index 000000000..d37b93e4f
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
new file mode 100644
index 000000000..93a16f146
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
new file mode 100644
index 000000000..ba0bfb30c
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
new file mode 100644
index 000000000..7ca4ceed5
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests-clar/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045
new file mode 100644
index 000000000..8953b6cef
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/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/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 b/tests-clar/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63
new file mode 100644
index 000000000..e9150214b
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests-clar/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644
new file mode 100644
index 000000000..c1f22c54f
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/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/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc b/tests-clar/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc
new file mode 100644
index 000000000..b669961d8
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 b/tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735
new file mode 100644
index 000000000..9ff5eb2b5
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
new file mode 100644
index 000000000..2ef4faa0f
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
new file mode 100644
index 000000000..2f9b6b6e3
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
new file mode 100644
index 000000000..5df58dda5
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests-clar/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a
new file mode 100644
index 000000000..a79612435
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/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/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests-clar/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f
new file mode 100644
index 000000000..f8588696b
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/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/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests-clar/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
new file mode 100644
index 000000000..d0d7e736e
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
new file mode 100644
index 000000000..18a7f61c2
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests-clar/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd
new file mode 100644
index 000000000..75f541f10
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/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/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 b/tests-clar/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9
new file mode 100644
index 000000000..7620c514f
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 b/tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83
new file mode 100644
index 000000000..00940f0f2
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
new file mode 100644
index 000000000..03770969a
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
new file mode 100644
index 000000000..112998d42
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
new file mode 100644
index 000000000..12bf5f3e3
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765
Binary files differ
diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep b/tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep
diff --git a/tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir b/tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir
new file mode 100644
index 000000000..4567d37fa
--- /dev/null
+++ b/tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir
@@ -0,0 +1 @@
+144344043ba4d4a405da03de3844aa829ae8be0e
diff --git a/tests-clar/resources/renames/.gitted/HEAD b/tests-clar/resources/renames/.gitted/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/renames/.gitted/config b/tests-clar/resources/renames/.gitted/config
new file mode 100644
index 000000000..bb4d11c1f
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/config
@@ -0,0 +1,7 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = false
+ logallrefupdates = true
+ ignorecase = true
+ precomposeunicode = false
diff --git a/tests-clar/resources/renames/.gitted/description b/tests-clar/resources/renames/.gitted/description
new file mode 100644
index 000000000..498b267a8
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/description
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests-clar/resources/renames/.gitted/index b/tests-clar/resources/renames/.gitted/index
new file mode 100644
index 000000000..1fc69fcbe
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/index
Binary files differ
diff --git a/tests-clar/resources/renames/.gitted/info/exclude b/tests-clar/resources/renames/.gitted/info/exclude
new file mode 100644
index 000000000..a5196d1be
--- /dev/null
+++ b/tests-clar/resources/renames/.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/renames/.gitted/logs/HEAD b/tests-clar/resources/renames/.gitted/logs/HEAD
new file mode 100644
index 000000000..34222ed7d
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/logs/HEAD
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 Russell Belfer <rb@github.com> 1351024687 -0700 commit (initial): Initial commit
+31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 2bc7f351d20b53f1c72c16c4b036e491c478c49a Russell Belfer <rb@github.com> 1351024817 -0700 commit: copy and rename with no change
diff --git a/tests-clar/resources/renames/.gitted/logs/refs/heads/master b/tests-clar/resources/renames/.gitted/logs/refs/heads/master
new file mode 100644
index 000000000..34222ed7d
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/logs/refs/heads/master
@@ -0,0 +1,2 @@
+0000000000000000000000000000000000000000 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 Russell Belfer <rb@github.com> 1351024687 -0700 commit (initial): Initial commit
+31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 2bc7f351d20b53f1c72c16c4b036e491c478c49a Russell Belfer <rb@github.com> 1351024817 -0700 commit: copy and rename with no change
diff --git a/tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 b/tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7
new file mode 100644
index 000000000..2ee86444d
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7
Binary files differ
diff --git a/tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a b/tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a
new file mode 100644
index 000000000..93f1ccb3f
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a
Binary files differ
diff --git a/tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 b/tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2
new file mode 100644
index 000000000..00ce53212
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2
Binary files differ
diff --git a/tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 b/tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745
new file mode 100644
index 000000000..24eac54c5
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745
Binary files differ
diff --git a/tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba b/tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba
new file mode 100644
index 000000000..5ee28a76a
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba
Binary files differ
diff --git a/tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 b/tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113
new file mode 100644
index 000000000..440b7bec3
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113
Binary files differ
diff --git a/tests-clar/resources/renames/.gitted/refs/heads/master b/tests-clar/resources/renames/.gitted/refs/heads/master
new file mode 100644
index 000000000..049b1f5ad
--- /dev/null
+++ b/tests-clar/resources/renames/.gitted/refs/heads/master
@@ -0,0 +1 @@
+2bc7f351d20b53f1c72c16c4b036e491c478c49a
diff --git a/tests-clar/resources/renames/sevencities.txt b/tests-clar/resources/renames/sevencities.txt
new file mode 100644
index 000000000..66311f5cf
--- /dev/null
+++ b/tests-clar/resources/renames/sevencities.txt
@@ -0,0 +1,49 @@
+The Song of Seven Cities
+========================
+
+I WAS Lord of Cities very sumptuously builded.
+Seven roaring Cities paid me tribute from afar.
+Ivory their outposts were—the guardrooms of them gilded,
+And garrisoned with Amazons invincible in war.
+
+All the world went softly when it walked before my Cities—
+Neither King nor Army vexed my peoples at their toil,
+Never horse nor chariot irked or overbore my Cities,
+Never Mob nor Ruler questioned whence they drew their spoil.
+
+Banded, mailed and arrogant from sunrise unto sunset;
+Singing while they sacked it, they possessed the land at large.
+Yet when men would rob them, they resisted, they made onset
+And pierced the smoke of battle with a thousand-sabred charge.
+
+So they warred and trafficked only yesterday, my Cities.
+To-day there is no mark or mound of where my Cities stood.
+For the River rose at midnight and it washed away my Cities.
+They are evened with Atlantis and the towns before the Flood.
+
+Rain on rain-gorged channels raised the water-levels round them,
+Freshet backed on freshet swelled and swept their world from sight,
+Till the emboldened floods linked arms and, flashing forward, drowned them—
+Drowned my Seven Cities and their peoples in one night!
+
+Low among the alders lie their derelict foundations,
+The beams wherein they trusted and the plinths whereon they built—
+My rulers and their treasure and their unborn populations,
+Dead, destroyed, aborted, and defiled with mud and silt!
+
+The Daughters of the Palace whom they cherished in my Cities,
+My silver-tongued Princesses, and the promise of their May—
+Their bridegrooms of the June-tide—all have perished in my Cities,
+With the harsh envenomed virgins that can neither love nor play.
+
+I was Lord of Cities—I will build anew my Cities,
+Seven, set on rocks, above the wrath of any flood.
+Nor will I rest from search till I have filled anew my Cities
+With peoples undefeated of the dark, enduring blood.
+
+To the sound of trumpets shall their seed restore my Cities
+Wealthy and well-weaponed, that once more may I behold
+All the world go softly when it walks before my Cities,
+And the horses and the chariots fleeing from them as of old!
+
+ -- Rudyard Kipling
diff --git a/tests-clar/resources/renames/sixserving.txt b/tests-clar/resources/renames/sixserving.txt
new file mode 100644
index 000000000..ad0a8e55a
--- /dev/null
+++ b/tests-clar/resources/renames/sixserving.txt
@@ -0,0 +1,24 @@
+I KEEP six honest serving-men
+ (They taught me all I knew);
+Their names are What and Why and When
+ And How and Where and Who.
+I send them over land and sea,
+ I send them east and west;
+But after they have worked for me,
+ I give them all a rest.
+
+I let them rest from nine till five,
+ For I am busy then,
+As well as breakfast, lunch, and tea,
+ For they are hungry men.
+But different folk have different views;
+I know a person small—
+She keeps ten million serving-men,
+Who get no rest at all!
+
+She sends'em abroad on her own affairs,
+ From the second she opens her eyes—
+One million Hows, two million Wheres,
+And seven million Whys!
+
+ -- Rudyard Kipling
diff --git a/tests-clar/resources/renames/songofseven.txt b/tests-clar/resources/renames/songofseven.txt
new file mode 100644
index 000000000..66311f5cf
--- /dev/null
+++ b/tests-clar/resources/renames/songofseven.txt
@@ -0,0 +1,49 @@
+The Song of Seven Cities
+========================
+
+I WAS Lord of Cities very sumptuously builded.
+Seven roaring Cities paid me tribute from afar.
+Ivory their outposts were—the guardrooms of them gilded,
+And garrisoned with Amazons invincible in war.
+
+All the world went softly when it walked before my Cities—
+Neither King nor Army vexed my peoples at their toil,
+Never horse nor chariot irked or overbore my Cities,
+Never Mob nor Ruler questioned whence they drew their spoil.
+
+Banded, mailed and arrogant from sunrise unto sunset;
+Singing while they sacked it, they possessed the land at large.
+Yet when men would rob them, they resisted, they made onset
+And pierced the smoke of battle with a thousand-sabred charge.
+
+So they warred and trafficked only yesterday, my Cities.
+To-day there is no mark or mound of where my Cities stood.
+For the River rose at midnight and it washed away my Cities.
+They are evened with Atlantis and the towns before the Flood.
+
+Rain on rain-gorged channels raised the water-levels round them,
+Freshet backed on freshet swelled and swept their world from sight,
+Till the emboldened floods linked arms and, flashing forward, drowned them—
+Drowned my Seven Cities and their peoples in one night!
+
+Low among the alders lie their derelict foundations,
+The beams wherein they trusted and the plinths whereon they built—
+My rulers and their treasure and their unborn populations,
+Dead, destroyed, aborted, and defiled with mud and silt!
+
+The Daughters of the Palace whom they cherished in my Cities,
+My silver-tongued Princesses, and the promise of their May—
+Their bridegrooms of the June-tide—all have perished in my Cities,
+With the harsh envenomed virgins that can neither love nor play.
+
+I was Lord of Cities—I will build anew my Cities,
+Seven, set on rocks, above the wrath of any flood.
+Nor will I rest from search till I have filled anew my Cities
+With peoples undefeated of the dark, enduring blood.
+
+To the sound of trumpets shall their seed restore my Cities
+Wealthy and well-weaponed, that once more may I behold
+All the world go softly when it walks before my Cities,
+And the horses and the chariots fleeing from them as of old!
+
+ -- Rudyard Kipling
diff --git a/tests-clar/resources/short_tag.git/HEAD b/tests-clar/resources/short_tag.git/HEAD
new file mode 100644
index 000000000..cb089cd89
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/short_tag.git/config b/tests-clar/resources/short_tag.git/config
new file mode 100644
index 000000000..a4ef456cb
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/config
@@ -0,0 +1,5 @@
+[core]
+ repositoryformatversion = 0
+ filemode = true
+ bare = true
+ logallrefupdates = true
diff --git a/tests-clar/resources/short_tag.git/index b/tests-clar/resources/short_tag.git/index
new file mode 100644
index 000000000..87fef7847
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/index
Binary files differ
diff --git a/tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c b/tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c
new file mode 100644
index 000000000..aeb4e4b0b
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c
Binary files differ
diff --git a/tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c b/tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c
new file mode 100644
index 000000000..806ce71a5
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c
Binary files differ
diff --git a/tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 b/tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729
new file mode 100644
index 000000000..1192707c9
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729
@@ -0,0 +1 @@
+xM0@aלb. i%1ƍpc@--6&B E+pVСSƆd/m(RJ% R^vʩ,Giǖ <Ӵ3\ncinRSg u1 \ No newline at end of file
diff --git a/tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 000000000..711223894
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests-clar/resources/short_tag.git/packed-refs b/tests-clar/resources/short_tag.git/packed-refs
new file mode 100644
index 000000000..ca5197e3c
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/packed-refs
@@ -0,0 +1 @@
+5da7760512a953e3c7c4e47e4392c7a4338fb729 refs/tags/no_description
diff --git a/tests-clar/resources/short_tag.git/refs/heads/master b/tests-clar/resources/short_tag.git/refs/heads/master
new file mode 100644
index 000000000..fcefd1ef0
--- /dev/null
+++ b/tests-clar/resources/short_tag.git/refs/heads/master
@@ -0,0 +1 @@
+4a5ed60bafcf4638b7c8356bd4ce1916bfede93c
diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config
index 54ff6109b..3801ce08d 100644
--- a/tests-clar/resources/testrepo.git/config
+++ b/tests-clar/resources/testrepo.git/config
@@ -8,6 +8,8 @@
fetch = +refs/heads/*:refs/remotes/test/*
[remote "joshaber"]
url = git://github.com/libgit2/libgit2
+[remote "empty-remote-url"]
+ url =
[remote "test_with_pushurl"]
url = git://github.com/libgit2/fetchlibgit2
diff --git a/tests-clar/stash/drop.c b/tests-clar/stash/drop.c
new file mode 100644
index 000000000..b4f73b995
--- /dev/null
+++ b/tests-clar/stash/drop.c
@@ -0,0 +1,126 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "stash_helpers.h"
+
+static git_repository *repo;
+static git_signature *signature;
+
+void test_stash_drop__initialize(void)
+{
+ cl_git_pass(git_repository_init(&repo, "stash", 0));
+ cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
+}
+
+void test_stash_drop__cleanup(void)
+{
+ git_signature_free(signature);
+ git_repository_free(repo);
+ cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+void test_stash_drop__cannot_drop_from_an_empty_stash(void)
+{
+ cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0));
+}
+
+static void push_three_states(void)
+{
+ git_oid oid;
+ git_index *index;
+
+ cl_git_mkfile("stash/zero.txt", "content\n");
+ cl_git_pass(git_repository_index(&index, repo));
+ cl_git_pass(git_index_add_from_workdir(index, "zero.txt"));
+ commit_staged_files(&oid, index, signature);
+
+ cl_git_mkfile("stash/one.txt", "content\n");
+ cl_git_pass(git_stash_save(&oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED));
+
+ cl_git_mkfile("stash/two.txt", "content\n");
+ cl_git_pass(git_stash_save(&oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED));
+
+ cl_git_mkfile("stash/three.txt", "content\n");
+ cl_git_pass(git_stash_save(&oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED));
+
+ git_index_free(index);
+}
+
+void test_stash_drop__cannot_drop_a_non_existing_stashed_state(void)
+{
+ push_three_states();
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 666));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 42));
+ cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 3));
+}
+
+void test_stash_drop__can_purge_the_stash_from_the_top(void)
+{
+ push_three_states();
+
+ cl_git_pass(git_stash_drop(repo, 0));
+ cl_git_pass(git_stash_drop(repo, 0));
+ cl_git_pass(git_stash_drop(repo, 0));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0));
+}
+
+void test_stash_drop__can_purge_the_stash_from_the_bottom(void)
+{
+ push_three_states();
+
+ cl_git_pass(git_stash_drop(repo, 2));
+ cl_git_pass(git_stash_drop(repo, 1));
+ cl_git_pass(git_stash_drop(repo, 0));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0));
+}
+
+void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void)
+{
+ git_reference *stash;
+ git_reflog *reflog;
+ const git_reflog_entry *entry;
+ git_oid oid;
+ size_t count;
+
+ push_three_states();
+
+ cl_git_pass(git_reference_lookup(&stash, repo, "refs/stash"));
+
+ cl_git_pass(git_reflog_read(&reflog, stash));
+ entry = git_reflog_entry_byindex(reflog, 1);
+
+ git_oid_cpy(&oid, git_reflog_entry_oidold(entry));
+ count = git_reflog_entrycount(reflog);
+
+ git_reflog_free(reflog);
+
+ cl_git_pass(git_stash_drop(repo, 1));
+
+ cl_git_pass(git_reflog_read(&reflog, stash));
+ entry = git_reflog_entry_byindex(reflog, 0);
+
+ cl_assert_equal_i(0, git_oid_cmp(&oid, git_reflog_entry_oidold(entry)));
+ cl_assert_equal_i(count - 1, git_reflog_entrycount(reflog));
+
+ git_reflog_free(reflog);
+
+ git_reference_free(stash);
+}
+
+void test_stash_drop__dropping_the_last_entry_removes_the_stash(void)
+{
+ git_reference *stash;
+
+ push_three_states();
+
+ cl_git_pass(git_reference_lookup(&stash, repo, "refs/stash"));
+ git_reference_free(stash);
+
+ cl_git_pass(git_stash_drop(repo, 0));
+ cl_git_pass(git_stash_drop(repo, 0));
+ cl_git_pass(git_stash_drop(repo, 0));
+
+ cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&stash, repo, "refs/stash"));
+}
diff --git a/tests-clar/stash/foreach.c b/tests-clar/stash/foreach.c
new file mode 100644
index 000000000..d7127a9db
--- /dev/null
+++ b/tests-clar/stash/foreach.c
@@ -0,0 +1,120 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "stash_helpers.h"
+
+struct callback_data
+{
+ char **oids;
+ int invokes;
+};
+
+static git_repository *repo;
+static git_signature *signature;
+static git_oid stash_tip_oid;
+struct callback_data data;
+
+#define REPO_NAME "stash"
+
+void test_stash_foreach__initialize(void)
+{
+ cl_git_pass(git_signature_new(
+ &signature,
+ "nulltoken",
+ "emeric.fermas@gmail.com",
+ 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
+
+ memset(&data, 0, sizeof(struct callback_data));
+}
+
+void test_stash_foreach__cleanup(void)
+{
+ git_signature_free(signature);
+ git_repository_free(repo);
+ cl_git_pass(git_futils_rmdir_r(REPO_NAME, NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+static int callback_cb(
+ size_t index,
+ const char* message,
+ const git_oid *stash_oid,
+ void *payload)
+{
+ struct callback_data *data = (struct callback_data *)payload;
+
+ GIT_UNUSED(index);
+ GIT_UNUSED(message);
+
+ cl_assert_equal_i(0, git_oid_streq(stash_oid, data->oids[data->invokes++]));
+
+ return 0;
+}
+
+void test_stash_foreach__enumerating_a_empty_repository_doesnt_fail(void)
+{
+ char *oids[] = { NULL };
+
+ data.oids = oids;
+
+ cl_git_pass(git_repository_init(&repo, REPO_NAME, 0));
+
+ cl_git_pass(git_stash_foreach(repo, callback_cb, &data));
+
+ cl_assert_equal_i(0, data.invokes);
+}
+
+void test_stash_foreach__can_enumerate_a_repository(void)
+{
+ char *oids_default[] = {
+ "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL };
+
+ char *oids_untracked[] = {
+ "7f89a8b15c878809c5c54d1ff8f8c9674154017b",
+ "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL };
+
+ char *oids_ignored[] = {
+ "c95599a8fef20a7e57582c6727b1a0d02e0a5828",
+ "7f89a8b15c878809c5c54d1ff8f8c9674154017b",
+ "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL };
+
+ cl_git_pass(git_repository_init(&repo, REPO_NAME, 0));
+
+ setup_stash(repo, signature);
+
+ cl_git_pass(git_stash_save(
+ &stash_tip_oid,
+ repo,
+ signature,
+ NULL,
+ GIT_STASH_DEFAULT));
+
+ data.oids = oids_default;
+
+ cl_git_pass(git_stash_foreach(repo, callback_cb, &data));
+ cl_assert_equal_i(1, data.invokes);
+
+ data.oids = oids_untracked;
+ data.invokes = 0;
+
+ cl_git_pass(git_stash_save(
+ &stash_tip_oid,
+ repo,
+ signature,
+ NULL,
+ GIT_STASH_INCLUDE_UNTRACKED));
+
+ cl_git_pass(git_stash_foreach(repo, callback_cb, &data));
+ cl_assert_equal_i(2, data.invokes);
+
+ data.oids = oids_ignored;
+ data.invokes = 0;
+
+ cl_git_pass(git_stash_save(
+ &stash_tip_oid,
+ repo,
+ signature,
+ NULL,
+ GIT_STASH_INCLUDE_IGNORED));
+
+ cl_git_pass(git_stash_foreach(repo, callback_cb, &data));
+ cl_assert_equal_i(3, data.invokes);
+}
diff --git a/tests-clar/stash/save.c b/tests-clar/stash/save.c
new file mode 100644
index 000000000..fadb8940b
--- /dev/null
+++ b/tests-clar/stash/save.c
@@ -0,0 +1,370 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "stash_helpers.h"
+
+static git_repository *repo;
+static git_signature *signature;
+static git_oid stash_tip_oid;
+
+/*
+ * Friendly reminder, in order to ease the reading of the following tests:
+ *
+ * "stash" points to the worktree commit
+ * "stash^1" points to the base commit (HEAD when the stash was created)
+ * "stash^2" points to the index commit
+ * "stash^3" points to the untracked commit
+ */
+
+void test_stash_save__initialize(void)
+{
+ cl_git_pass(git_repository_init(&repo, "stash", 0));
+ cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
+
+ setup_stash(repo, signature);
+}
+
+void test_stash_save__cleanup(void)
+{
+ git_signature_free(signature);
+ git_repository_free(repo);
+ cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES));
+}
+
+static void assert_object_oid(const char* revision, const char* expected_oid, git_otype type)
+{
+ git_object *object;
+ int result;
+
+ result = git_revparse_single(&object, repo, revision);
+
+ if (!expected_oid) {
+ cl_assert_equal_i(GIT_ENOTFOUND, result);
+ return;
+ } else
+ cl_assert_equal_i(0, result);
+
+ cl_assert_equal_i(type, git_object_type(object));
+ cl_git_pass(git_oid_streq(git_object_id(object), expected_oid));
+
+ git_object_free(object);
+}
+
+static void assert_blob_oid(const char* revision, const char* expected_oid)
+{
+ assert_object_oid(revision, expected_oid, GIT_OBJ_BLOB);
+}
+
+void test_stash_save__does_not_keep_index_by_default(void)
+{
+/*
+$ git stash
+
+$ git show refs/stash:what
+see you later
+
+$ git show refs/stash:how
+not so small and
+
+$ git show refs/stash:who
+funky world
+
+$ git show refs/stash:when
+fatal: Path 'when' exists on disk, but not in 'stash'.
+
+$ git show refs/stash^2:what
+goodbye
+
+$ git show refs/stash^2:how
+not so small and
+
+$ git show refs/stash^2:who
+world
+
+$ git show refs/stash^2:when
+fatal: Path 'when' exists on disk, but not in 'stash^2'.
+
+$ git status --short
+?? when
+
+*/
+ unsigned int status;
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+ cl_git_pass(git_status_file(&status, repo, "when"));
+
+ assert_blob_oid("refs/stash:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */
+ assert_blob_oid("refs/stash:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */
+ assert_blob_oid("refs/stash:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */
+ assert_blob_oid("refs/stash:when", NULL);
+ assert_blob_oid("refs/stash:just.ignore", NULL);
+
+ assert_blob_oid("refs/stash^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */
+ assert_blob_oid("refs/stash^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */
+ assert_blob_oid("refs/stash^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */
+ assert_blob_oid("refs/stash^2:when", NULL);
+ assert_blob_oid("refs/stash^2:just.ignore", NULL);
+
+ assert_blob_oid("refs/stash^3", NULL);
+
+ cl_assert_equal_i(GIT_STATUS_WT_NEW, status);
+}
+
+static void assert_status(
+ const char *path,
+ int status_flags)
+{
+ unsigned int status;
+ int error;
+
+ error = git_status_file(&status, repo, path);
+
+ if (status_flags < 0) {
+ cl_assert_equal_i(status_flags, error);
+ return;
+ }
+
+ cl_assert_equal_i(0, error);
+ cl_assert_equal_i((unsigned int)status_flags, status);
+}
+
+void test_stash_save__can_keep_index(void)
+{
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_KEEP_INDEX));
+
+ assert_status("what", GIT_STATUS_INDEX_MODIFIED);
+ assert_status("how", GIT_STATUS_INDEX_MODIFIED);
+ assert_status("who", GIT_STATUS_CURRENT);
+ assert_status("when", GIT_STATUS_WT_NEW);
+ assert_status("just.ignore", GIT_STATUS_IGNORED);
+}
+
+static void assert_commit_message_contains(const char *revision, const char *fragment)
+{
+ git_commit *commit;
+
+ cl_git_pass(git_revparse_single(((git_object **)&commit), repo, revision));
+
+ cl_assert(strstr(git_commit_message(commit), fragment) != NULL);
+
+ git_commit_free(commit);
+}
+
+void test_stash_save__can_include_untracked_files(void)
+{
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+
+ assert_commit_message_contains("refs/stash^3", "untracked files on master: ");
+
+ assert_blob_oid("refs/stash^3:what", NULL);
+ assert_blob_oid("refs/stash^3:how", NULL);
+ assert_blob_oid("refs/stash^3:who", NULL);
+ assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b");
+ assert_blob_oid("refs/stash^3:just.ignore", NULL);
+}
+
+void test_stash_save__can_include_untracked_and_ignored_files(void)
+{
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED));
+
+ assert_commit_message_contains("refs/stash^3", "untracked files on master: ");
+
+ assert_blob_oid("refs/stash^3:what", NULL);
+ assert_blob_oid("refs/stash^3:how", NULL);
+ assert_blob_oid("refs/stash^3:who", NULL);
+ assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b");
+ assert_blob_oid("refs/stash^3:just.ignore", "78925fb1236b98b37a35e9723033e627f97aa88b");
+}
+
+#define MESSAGE "Look Ma! I'm on TV!"
+void test_stash_save__can_accept_a_message(void)
+{
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, MESSAGE, GIT_STASH_DEFAULT));
+
+ assert_commit_message_contains("refs/stash^2", "index on master: ");
+ assert_commit_message_contains("refs/stash", "On master: " MESSAGE);
+}
+
+void test_stash_save__cannot_stash_against_an_unborn_branch(void)
+{
+ git_reference *head;
+
+ cl_git_pass(git_reference_lookup(&head, repo, "HEAD"));
+ cl_git_pass(git_reference_set_target(head, "refs/heads/unborn"));
+
+ cl_assert_equal_i(GIT_EORPHANEDHEAD,
+ git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+
+ git_reference_free(head);
+}
+
+void test_stash_save__cannot_stash_against_a_bare_repository(void)
+{
+ git_repository *local;
+
+ cl_git_pass(git_repository_init(&local, "sorry-it-is-a-non-bare-only-party", 1));
+
+ cl_assert_equal_i(GIT_EBAREREPO,
+ git_stash_save(&stash_tip_oid, local, signature, NULL, GIT_STASH_DEFAULT));
+
+ git_repository_free(local);
+}
+
+void test_stash_save__can_stash_against_a_detached_head(void)
+{
+ git_repository_detach_head(repo);
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+
+ assert_commit_message_contains("refs/stash^2", "index on (no branch): ");
+ assert_commit_message_contains("refs/stash", "WIP on (no branch): ");
+}
+
+void test_stash_save__stashing_updates_the_reflog(void)
+{
+ char *sha;
+
+ assert_object_oid("refs/stash@{0}", NULL, GIT_OBJ_COMMIT);
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+
+ sha = git_oid_allocfmt(&stash_tip_oid);
+
+ assert_object_oid("refs/stash@{0}", sha, GIT_OBJ_COMMIT);
+ assert_object_oid("refs/stash@{1}", NULL, GIT_OBJ_COMMIT);
+
+ git__free(sha);
+}
+
+void test_stash_save__cannot_stash_when_there_are_no_local_change(void)
+{
+ git_index *index;
+ git_oid commit_oid, stash_tip_oid;
+
+ cl_git_pass(git_repository_index(&index, repo));
+
+ /*
+ * 'what' and 'who' are being committed.
+ * 'when' remain untracked.
+ */
+ cl_git_pass(git_index_add_from_workdir(index, "what"));
+ cl_git_pass(git_index_add_from_workdir(index, "who"));
+ cl_git_pass(git_index_write(index));
+ commit_staged_files(&commit_oid, index, signature);
+ git_index_free(index);
+
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+
+ p_unlink("stash/when");
+ cl_assert_equal_i(GIT_ENOTFOUND,
+ git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+}
+
+void test_stash_save__can_stage_normal_then_stage_untracked(void)
+{
+ /*
+ * $ git ls-tree stash@{1}^0
+ * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore
+ * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how
+ * 100644 blob bc99dc98b3eba0e9157e94769cd4d49cb49de449 what
+ * 100644 blob a0400d4954659306a976567af43125a0b1aa8595 who
+ *
+ * $ git ls-tree stash@{1}^1
+ * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore
+ * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how
+ * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what
+ * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who
+ *
+ * $ git ls-tree stash@{1}^2
+ * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore
+ * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how
+ * 100644 blob dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 what
+ * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who
+ *
+ * $ git ls-tree stash@{1}^3
+ * fatal: Not a valid object name stash@{1}^3
+ *
+ * $ git ls-tree stash@{0}^0
+ * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore
+ * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how
+ * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what
+ * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who
+ *
+ * $ git ls-tree stash@{0}^1
+ * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore
+ * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how
+ * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what
+ * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who
+ *
+ * $ git ls-tree stash@{0}^2
+ * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore
+ * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how
+ * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what
+ * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who
+ *
+ * $ git ls-tree stash@{0}^3
+ * 100644 blob b6ed15e81e2593d7bb6265eb4a991d29dc3e628b when
+ */
+
+ assert_status("what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED);
+ assert_status("how", GIT_STATUS_INDEX_MODIFIED);
+ assert_status("who", GIT_STATUS_WT_MODIFIED);
+ assert_status("when", GIT_STATUS_WT_NEW);
+ assert_status("just.ignore", GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT));
+ assert_status("what", GIT_STATUS_CURRENT);
+ assert_status("how", GIT_STATUS_CURRENT);
+ assert_status("who", GIT_STATUS_CURRENT);
+ assert_status("when", GIT_STATUS_WT_NEW);
+ assert_status("just.ignore", GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+ assert_status("what", GIT_STATUS_CURRENT);
+ assert_status("how", GIT_STATUS_CURRENT);
+ assert_status("who", GIT_STATUS_CURRENT);
+ assert_status("when", GIT_ENOTFOUND);
+ assert_status("just.ignore", GIT_STATUS_IGNORED);
+
+
+ assert_blob_oid("stash@{1}^0:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */
+ assert_blob_oid("stash@{1}^0:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */
+ assert_blob_oid("stash@{1}^0:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */
+ assert_blob_oid("stash@{1}^0:when", NULL);
+
+ assert_blob_oid("stash@{1}^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */
+ assert_blob_oid("stash@{1}^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */
+ assert_blob_oid("stash@{1}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */
+ assert_blob_oid("stash@{1}^2:when", NULL);
+
+ assert_object_oid("stash@{1}^3", NULL, GIT_OBJ_COMMIT);
+
+ assert_blob_oid("stash@{0}^0:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */
+ assert_blob_oid("stash@{0}^0:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */
+ assert_blob_oid("stash@{0}^0:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */
+ assert_blob_oid("stash@{0}^0:when", NULL);
+
+ assert_blob_oid("stash@{0}^2:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */
+ assert_blob_oid("stash@{0}^2:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */
+ assert_blob_oid("stash@{0}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */
+ assert_blob_oid("stash@{0}^2:when", NULL);
+
+ assert_blob_oid("stash@{0}^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); /* now */
+}
+
+#define EMPTY_TREE "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
+
+void test_stash_save__including_untracked_without_any_untracked_file_creates_an_empty_tree(void)
+{
+ cl_git_pass(p_unlink("stash/when"));
+
+ assert_status("what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED);
+ assert_status("how", GIT_STATUS_INDEX_MODIFIED);
+ assert_status("who", GIT_STATUS_WT_MODIFIED);
+ assert_status("when", GIT_ENOTFOUND);
+ assert_status("just.ignore", GIT_STATUS_IGNORED);
+
+ cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+
+ assert_object_oid("stash^3^{tree}", EMPTY_TREE, GIT_OBJ_TREE);
+}
diff --git a/tests-clar/stash/stash_helpers.c b/tests-clar/stash/stash_helpers.c
new file mode 100644
index 000000000..86a741853
--- /dev/null
+++ b/tests-clar/stash/stash_helpers.c
@@ -0,0 +1,68 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "stash_helpers.h"
+
+void commit_staged_files(
+ git_oid *commit_oid,
+ git_index *index,
+ git_signature *signature)
+{
+ git_tree *tree;
+ git_oid tree_oid;
+ git_repository *repo;
+
+ repo = git_index_owner(index);
+
+ cl_git_pass(git_index_write_tree(&tree_oid, index));
+
+ cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid));
+
+ cl_git_pass(git_commit_create_v(
+ commit_oid,
+ repo,
+ "HEAD",
+ signature,
+ signature,
+ NULL,
+ "Initial commit",
+ tree,
+ 0));
+
+ git_tree_free(tree);
+}
+
+void setup_stash(git_repository *repo, git_signature *signature)
+{
+ git_oid commit_oid;
+ git_index *index;
+
+ cl_git_pass(git_repository_index(&index, repo));
+
+ cl_git_mkfile("stash/what", "hello\n"); /* ce013625030ba8dba906f756967f9e9ca394464a */
+ cl_git_mkfile("stash/how", "small\n"); /* ac790413e2d7a26c3767e78c57bb28716686eebc */
+ cl_git_mkfile("stash/who", "world\n"); /* cc628ccd10742baea8241c5924df992b5c019f71 */
+ cl_git_mkfile("stash/when", "now\n"); /* b6ed15e81e2593d7bb6265eb4a991d29dc3e628b */
+ cl_git_mkfile("stash/just.ignore", "me\n"); /* 78925fb1236b98b37a35e9723033e627f97aa88b */
+
+ cl_git_mkfile("stash/.gitignore", "*.ignore\n");
+
+ cl_git_pass(git_index_add_from_workdir(index, "what"));
+ cl_git_pass(git_index_add_from_workdir(index, "how"));
+ cl_git_pass(git_index_add_from_workdir(index, "who"));
+ cl_git_pass(git_index_add_from_workdir(index, ".gitignore"));
+ cl_git_pass(git_index_write(index));
+
+ commit_staged_files(&commit_oid, index, signature);
+
+ cl_git_rewritefile("stash/what", "goodbye\n"); /* dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 */
+ cl_git_rewritefile("stash/how", "not so small and\n"); /* e6d64adb2c7f3eb8feb493b556cc8070dca379a3 */
+ cl_git_rewritefile("stash/who", "funky world\n"); /* a0400d4954659306a976567af43125a0b1aa8595 */
+
+ cl_git_pass(git_index_add_from_workdir(index, "what"));
+ cl_git_pass(git_index_add_from_workdir(index, "how"));
+ cl_git_pass(git_index_write(index));
+
+ cl_git_rewritefile("stash/what", "see you later\n"); /* bc99dc98b3eba0e9157e94769cd4d49cb49de449 */
+
+ git_index_free(index);
+}
diff --git a/tests-clar/stash/stash_helpers.h b/tests-clar/stash/stash_helpers.h
new file mode 100644
index 000000000..bb7fec4f5
--- /dev/null
+++ b/tests-clar/stash/stash_helpers.h
@@ -0,0 +1,8 @@
+void setup_stash(
+ git_repository *repo,
+ git_signature *signature);
+
+void commit_staged_files(
+ git_oid *commit_oid,
+ git_index *index,
+ git_signature *signature); \ No newline at end of file
diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c
index 908d34510..c154179b0 100644
--- a/tests-clar/status/worktree.c
+++ b/tests-clar/status/worktree.c
@@ -71,7 +71,7 @@ static int remove_file_cb(void *data, git_buf *file)
return 0;
if (git_path_isdir(filename))
- cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_RMDIR_REMOVE_FILES));
else
cl_git_pass(p_unlink(git_buf_cstr(file)));
@@ -314,7 +314,7 @@ void test_status_worktree__issue_592_3(void)
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c"));
- cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
@@ -344,7 +344,7 @@ void test_status_worktree__issue_592_5(void)
repo = cl_git_sandbox_init("issue_592");
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t"));
- cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777));
cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));
@@ -438,7 +438,7 @@ void test_status_worktree__first_commit_in_progress(void)
cl_assert(result.status == GIT_STATUS_WT_NEW);
cl_git_pass(git_repository_index(&index, repo));
- cl_git_pass(git_index_add(index, "testfile.txt", 0));
+ cl_git_pass(git_index_add_from_workdir(index, "testfile.txt"));
cl_git_pass(git_index_write(index));
memset(&result, 0, sizeof(result));
@@ -486,7 +486,7 @@ static void fill_index_wth_head_entries(git_repository *repo, git_index *index)
cl_git_pass(git_commit_lookup(&commit, repo, &oid));
cl_git_pass(git_commit_tree(&tree, commit));
- cl_git_pass(git_index_read_tree(index, tree, NULL));
+ cl_git_pass(git_index_read_tree(index, tree));
cl_git_pass(git_index_write(index));
git_tree_free(tree);
@@ -570,7 +570,7 @@ void test_status_worktree__bracket_in_filename(void)
/* add the file to the index */
cl_git_pass(git_repository_index(&index, repo));
- cl_git_pass(git_index_add(index, FILE_WITH_BRACKET, 0));
+ cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_BRACKET));
cl_git_pass(git_index_write(index));
memset(&result, 0, sizeof(result));
@@ -648,7 +648,7 @@ void test_status_worktree__space_in_filename(void)
/* add the file to the index */
cl_git_pass(git_repository_index(&index, repo));
- cl_git_pass(git_index_add(index, FILE_WITH_SPACE, 0));
+ cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_SPACE));
cl_git_pass(git_index_write(index));
memset(&result, 0, sizeof(result));
@@ -816,7 +816,7 @@ void test_status_worktree__new_staged_file_must_handle_crlf(void)
cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); // Content with CRLF
cl_git_pass(git_repository_index(&index, repo));
- cl_git_pass(git_index_add(index, "testfile.txt", 0));
+ cl_git_pass(git_index_add_from_workdir(index, "testfile.txt"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_status_file(&status, repo, "testfile.txt"));
diff --git a/tests-clar/submodule/status.c b/tests-clar/submodule/status.c
index 63073ceca..325013466 100644
--- a/tests-clar/submodule/status.c
+++ b/tests-clar/submodule/status.c
@@ -50,7 +50,7 @@ void test_submodule_status__ignore_none(void)
git_buf path = GIT_BUF_INIT;
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
- cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule"));
@@ -105,7 +105,7 @@ void test_submodule_status__ignore_none(void)
cl_git_pass(git_repository_index(&index, g_repo));
pos = git_index_find(index, "sm_changed_head");
cl_assert(pos >= 0);
- cl_git_pass(git_index_remove(index, pos));
+ cl_git_pass(git_index_remove(index, "sm_changed_head", 0));
cl_git_pass(git_index_write(index));
git_index_free(index);
@@ -135,7 +135,7 @@ void test_submodule_status__ignore_untracked(void)
git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED;
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
- cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
@@ -195,7 +195,7 @@ void test_submodule_status__ignore_dirty(void)
git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY;
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
- cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
@@ -255,7 +255,7 @@ void test_submodule_status__ignore_all(void)
git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL;
cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
- cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
+ cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES));
cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));